forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p' (head 2da3b585b42d058e25909bc303d72277ae2463b5)
to branch 'i2p.i2p.zzz.update' (head ebbad994215dc2822e9a1776399864ed77a0e5a0)
This commit is contained in:
@ -330,6 +330,118 @@
|
||||
splitindex="true"
|
||||
windowtitle="Router Console" />
|
||||
</target>
|
||||
|
||||
<!-- scala paths -->
|
||||
<target name="scala.init">
|
||||
<property name="scala-library.jar" value="${scalatest.libs}/scala-library.jar" />
|
||||
<property name="scalatest.jar" value="${scalatest.libs}/scalatest.jar" />
|
||||
<taskdef resource="scala/tools/ant/antlib.xml">
|
||||
<classpath>
|
||||
<pathelement location="${scalatest.libs}/scala-compiler.jar" />
|
||||
<pathelement location="${scala-library.jar}" />
|
||||
</classpath>
|
||||
</taskdef>
|
||||
</target>
|
||||
|
||||
<!-- unit tests -->
|
||||
<target name="builddepscalatest">
|
||||
<ant dir="../../../router/java/" target="jar" />
|
||||
<ant dir="../../../router/java/" target="jarScalaTest" />
|
||||
</target>
|
||||
<target name="scalatest.compileTest" depends="builddepscalatest, compile, scala.init">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj_scala" />
|
||||
<scalac srcdir="./test/scalatest" destdir="./build/obj_scala" deprecation="on" >
|
||||
<classpath>
|
||||
<pathelement location="${classpath}" />
|
||||
<pathelement location="${scala-library.jar}" />
|
||||
<pathelement location="${scalatest.jar}" />
|
||||
<pathelement location="../../../core/java/build/i2pscalatest.jar" />
|
||||
<pathelement location="../../../router/java/build/routerscalatest.jar" />
|
||||
<pathelement location="./build/obj" />
|
||||
</classpath>
|
||||
</scalac>
|
||||
</target>
|
||||
<!-- preparation of code coverage tool of choice -->
|
||||
<target name="prepareClover" depends="compile" if="with.clover">
|
||||
<taskdef resource="clovertasks"/>
|
||||
<mkdir dir="../../../reports/apps/routerconsole/clover" />
|
||||
<clover-setup initString="../../../reports/apps/routerconsole/clover/coverage.db"/>
|
||||
</target>
|
||||
<target name="prepareCobertura" depends="compile" if="with.cobertura">
|
||||
<taskdef classpath="${with.cobertura}" resource="tasks.properties" onerror="report" />
|
||||
<mkdir dir="./build/obj_cobertura" />
|
||||
<delete file="./cobertura.ser" />
|
||||
<cobertura-instrument todir="./build/obj_cobertura">
|
||||
<fileset dir="./build/obj">
|
||||
<include name="**/*.class"/>
|
||||
<exclude name="**/*Test.class" />
|
||||
</fileset>
|
||||
</cobertura-instrument>
|
||||
</target>
|
||||
<target name="prepareTest" depends="prepareClover, prepareCobertura" />
|
||||
<!-- end preparation of code coverage tool -->
|
||||
<target name="scalatest.test" depends="clean, scalatest.compileTest, prepareTest">
|
||||
<mkdir dir="../../../reports/apps/routerconsole/scalatest/" />
|
||||
<delete>
|
||||
<fileset dir="../../../reports/apps/routerconsole/scalatest">
|
||||
<include name="TEST-*.xml"/>
|
||||
</fileset>
|
||||
</delete>
|
||||
<taskdef name="scalatest" classname="org.scalatest.tools.ScalaTestAntTask">
|
||||
<classpath>
|
||||
<pathelement location="${classpath}" />
|
||||
<pathelement location="${scala-library.jar}" />
|
||||
<pathelement location="${scalatest.jar}" />
|
||||
<pathelement location="./build/obj_cobertura" />
|
||||
<pathelement location="./build/obj" />
|
||||
<pathelement location="${with.clover}" />
|
||||
<pathelement location="${with.cobertura}" />
|
||||
</classpath>
|
||||
</taskdef>
|
||||
<scalatest runpath="./build/obj_scala" fork="yes" maxmemory="384M">
|
||||
<tagsToExclude>
|
||||
SlowTests
|
||||
</tagsToExclude>
|
||||
<reporter type="stdout" />
|
||||
<reporter type="junitxml" directory="../../../reports/apps/routerconsole/scalatest/" />
|
||||
</scalatest>
|
||||
<!-- fetch the real hostname of this machine -->
|
||||
<exec executable="hostname" outputproperty="host.name"/>
|
||||
<!-- set if unset -->
|
||||
<property name="host.fakename" value="i2ptester" />
|
||||
<!-- replace hostname that junit inserts into reports with fake one -->
|
||||
<replace dir="../../../reports/apps/routerconsole/scalatest/" token="${host.name}" value="${host.fakename}"/>
|
||||
</target>
|
||||
<target name="test" depends="scalatest.test"/>
|
||||
<!-- test reports -->
|
||||
<target name="scalatest.report">
|
||||
<junitreport todir="../../../reports/apps/routerconsole/scalatest">
|
||||
<fileset dir="../../../reports/apps/routerconsole/scalatest">
|
||||
<include name="TEST-*.xml"/>
|
||||
</fileset>
|
||||
<report format="frames" todir="../../../reports/apps/routerconsole/html/scalatest"/>
|
||||
</junitreport>
|
||||
</target>
|
||||
<target name="clover.report" depends="test" if="with.clover">
|
||||
<clover-report>
|
||||
<current outfile="../../../reports/apps/routerconsole/html/clover">
|
||||
<format type="html"/>
|
||||
</current>
|
||||
</clover-report>
|
||||
</target>
|
||||
<target name="cobertura.report" depends="test" if="with.cobertura">
|
||||
<mkdir dir="../../../reports/apps/routerconsole/cobertura" />
|
||||
<cobertura-report format="xml" srcdir="./src" destdir="../../../reports/apps/routerconsole/cobertura" />
|
||||
<mkdir dir="../../../reports/apps/routerconsole/html/cobertura" />
|
||||
<cobertura-report format="html" srcdir="./src" destdir="../../../reports/apps/routerconsole/html/cobertura" />
|
||||
<delete file="./cobertura.ser" />
|
||||
</target>
|
||||
<target name="test.report" depends="scalatest.report, clover.report, cobertura.report"/>
|
||||
<!-- end test reports -->
|
||||
<target name="fulltest" depends="cleandep, test, test.report" />
|
||||
<!-- end unit tests -->
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
<delete dir="../jsp/WEB-INF/" />
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,68 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.update.*;
|
||||
|
||||
/**
|
||||
* Dummy to lock up the updates for a period of time
|
||||
*
|
||||
* @since 0.9.2
|
||||
*/
|
||||
class DummyHandler implements Updater {
|
||||
private final RouterContext _context;
|
||||
|
||||
public DummyHandler(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spins off an UpdateTask that sleeps
|
||||
*/
|
||||
public UpdateTask check(UpdateType type, UpdateMethod method,
|
||||
String id, String currentVersion, long maxTime) {
|
||||
if (type != UpdateType.TYPE_DUMMY)
|
||||
return null;
|
||||
return new DummyRunner(_context, maxTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spins off an UpdateTask that sleeps
|
||||
*/
|
||||
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
|
||||
String id, String newVersion, long maxTime) {
|
||||
if (type != UpdateType.TYPE_DUMMY)
|
||||
return null;
|
||||
return new DummyRunner(_context, maxTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use for both check and update
|
||||
*/
|
||||
private static class DummyRunner extends UpdateRunner {
|
||||
private final long _delay;
|
||||
|
||||
public DummyRunner(RouterContext ctx, long maxTime) {
|
||||
super(ctx, Collections.EMPTY_LIST);
|
||||
_delay = maxTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateType getType() { return UpdateType.TYPE_DUMMY; }
|
||||
|
||||
@Override
|
||||
protected void update() {
|
||||
try {
|
||||
Thread.sleep(_delay);
|
||||
} catch (InterruptedException ie) {}
|
||||
UpdateManager mgr = _context.updateManager();
|
||||
if (mgr != null) {
|
||||
mgr.notifyCheckComplete(this, false, false);
|
||||
mgr.notifyTaskFailed(this, "dummy", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
import net.i2p.router.util.RFC822Date;
|
||||
import net.i2p.router.web.ConfigUpdateHandler;
|
||||
import net.i2p.router.web.ConfigUpdateHelper;
|
||||
import net.i2p.router.web.NewsHelper;
|
||||
import net.i2p.update.*;
|
||||
import static net.i2p.update.UpdateType.*;
|
||||
import static net.i2p.update.UpdateMethod.*;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.EepHead;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Task to fetch updates to the news.xml, and to keep
|
||||
* track of whether that has an announcement for a new version.
|
||||
*
|
||||
* @since 0.9.2 moved from NewsFetcher and make an Updater
|
||||
*/
|
||||
class NewsFetcher extends UpdateRunner {
|
||||
private String _lastModified;
|
||||
private final File _newsFile;
|
||||
private final File _tempFile;
|
||||
|
||||
private static final String TEMP_NEWS_FILE = "news.xml.temp";
|
||||
|
||||
public NewsFetcher(RouterContext ctx, List<URI> uris) {
|
||||
super(ctx, uris);
|
||||
_newsFile = new File(ctx.getRouterDir(), NewsHelper.NEWS_FILE);
|
||||
_tempFile = new File(ctx.getTempDir(), "tmp-" + ctx.random().nextLong() + TEMP_NEWS_FILE);
|
||||
long lastMod = NewsHelper.lastChecked(ctx);
|
||||
if (lastMod > 0)
|
||||
_lastModified = RFC822Date.to822Date(lastMod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateType getType() { return NEWS; }
|
||||
|
||||
private boolean dontInstall() {
|
||||
return NewsHelper.dontInstall(_context);
|
||||
}
|
||||
|
||||
private boolean shouldInstall() {
|
||||
String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
|
||||
if ("notify".equals(policy) || dontInstall())
|
||||
return false;
|
||||
File zip = new File(_context.getRouterDir(), Router.UPDATE_FILE);
|
||||
return !zip.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
fetchNews();
|
||||
}
|
||||
|
||||
public void fetchNews() {
|
||||
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
|
||||
|
||||
for (URI uri : _urls) {
|
||||
_currentURI = uri;
|
||||
String newsURL = uri.toString();
|
||||
|
||||
if (_tempFile.exists())
|
||||
_tempFile.delete();
|
||||
|
||||
try {
|
||||
EepGet get;
|
||||
if (shouldProxy)
|
||||
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
||||
else
|
||||
get = new EepGet(_context, false, null, 0, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
||||
get.addStatusListener(this);
|
||||
if (get.fetch()) {
|
||||
String lastMod = get.getLastModified();
|
||||
if (lastMod != null) {
|
||||
_lastModified = lastMod;
|
||||
long lm = RFC822Date.parse822Date(lastMod);
|
||||
if (lm > 0)
|
||||
_context.router().saveConfig(NewsHelper.PROP_LAST_CHECKED, Long.toString(lm));
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error fetching the news", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final String VERSION_STRING = "version=\"" + RouterVersion.VERSION + "\"";
|
||||
private static final String VERSION_PREFIX = "version=\"";
|
||||
|
||||
///// move to UpdateManager?
|
||||
|
||||
/**
|
||||
* Parse the installed (not the temp) news file for the latest version.
|
||||
* TODO: Real XML parsing, different update methods,
|
||||
* URLs in the file, ...
|
||||
*/
|
||||
void checkForUpdates() {
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(_newsFile);
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
while (DataHelper.readLine(in, buf)) {
|
||||
int index = buf.indexOf(VERSION_PREFIX);
|
||||
if (index >= 0) {
|
||||
int end = buf.indexOf("\"", index + VERSION_PREFIX.length());
|
||||
if (end > index) {
|
||||
String ver = buf.substring(index+VERSION_PREFIX.length(), end);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Found version: [" + ver + "]");
|
||||
if (TrustedUpdate.needsUpdate(RouterVersion.VERSION, ver)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Our version is out of date, update!");
|
||||
_mgr.notifyVersionAvailable(this, _currentURI,
|
||||
ROUTER_SIGNED, "", HTTP,
|
||||
_mgr.getUpdateURLs(ROUTER_SIGNED, "", HTTP),
|
||||
ver, "");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Our version is current");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (buf.indexOf(VERSION_STRING) == -1) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("No match in " + buf.toString());
|
||||
}
|
||||
buf.setLength(0);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error checking the news for an update", ioe);
|
||||
return;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No version found in news.xml file");
|
||||
}
|
||||
|
||||
/** override to prevent status update */
|
||||
@Override
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
|
||||
|
||||
/**
|
||||
* Copies the file from temp dir to the news location,
|
||||
* calls checkForUpdates()
|
||||
*/
|
||||
@Override
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
|
||||
|
||||
long now = _context.clock().now();
|
||||
if (_tempFile.exists()) {
|
||||
boolean copied = FileUtil.copy(_tempFile, _newsFile, true, false);
|
||||
_tempFile.delete();
|
||||
if (copied) {
|
||||
String newVer = Long.toString(now);
|
||||
_context.router().saveConfig(NewsHelper.PROP_LAST_UPDATED, newVer);
|
||||
_mgr.notifyVersionAvailable(this, _currentURI, NEWS, "", HTTP,
|
||||
null, newVer, "");
|
||||
checkForUpdates();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Failed to copy the news file!");
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Transfer complete, but no file? - probably 304 Not Modified");
|
||||
}
|
||||
}
|
||||
|
||||
/** override to prevent status update */
|
||||
@Override
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.web.ConfigUpdateHelper;
|
||||
import net.i2p.update.*;
|
||||
|
||||
/**
|
||||
* Task to periodically look for updates to the news.xml, and to keep
|
||||
* track of whether that has an announcement for a new version.
|
||||
*
|
||||
* @since 0.9.2 moved from NewsFetcher
|
||||
*/
|
||||
class NewsHandler extends UpdateHandler {
|
||||
|
||||
/** @since 0.7.14 not configurable */
|
||||
private static final String BACKUP_NEWS_URL = "http://www.i2p2.i2p/_static/news/news.xml";
|
||||
|
||||
public NewsHandler(RouterContext ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will check for news or router updates (it does the same thing).
|
||||
* Should not block.
|
||||
* @param currentVersion ignored, stored locally
|
||||
*/
|
||||
public UpdateTask check(UpdateType type, UpdateMethod method,
|
||||
String id, String currentVersion, long maxTime) {
|
||||
if ((type != UpdateType.ROUTER_SIGNED && type != UpdateType.ROUTER_SIGNED_PACK200 && type != UpdateType.NEWS) ||
|
||||
method != UpdateMethod.HTTP)
|
||||
return null;
|
||||
List<URI> updateSources = new ArrayList(2);
|
||||
try {
|
||||
updateSources.add(new URI(ConfigUpdateHelper.getNewsURL(_context)));
|
||||
} catch (URISyntaxException use) {}
|
||||
try {
|
||||
updateSources.add(new URI(BACKUP_NEWS_URL));
|
||||
} catch (URISyntaxException use) {}
|
||||
UpdateRunner update = new NewsFetcher(_context, updateSources);
|
||||
update.start();
|
||||
return update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing. check() also does update.
|
||||
*/
|
||||
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
|
||||
String id, String newVersion, long maxTime) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
import net.i2p.router.web.ConfigUpdateHandler;
|
||||
import net.i2p.router.web.ConfigUpdateHelper;
|
||||
import net.i2p.router.web.NewsHelper;
|
||||
import static net.i2p.update.UpdateType.*;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.EepHead;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
* Task to periodically look for updates to the news.xml, and to keep
|
||||
* track of whether that has an announcement for a new version.
|
||||
* Also looks for unsigned updates.
|
||||
*
|
||||
* Runs forever on instantiation, can't be stopped.
|
||||
*
|
||||
* @since 0.9.2 moved from NewsFetcher
|
||||
*/
|
||||
class NewsTimerTask implements SimpleTimer.TimedEvent {
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
private final ConsoleUpdateManager _mgr;
|
||||
|
||||
private static final long INITIAL_DELAY = 5*60*1000;
|
||||
private static final long RUN_DELAY = 10*60*1000;
|
||||
|
||||
public NewsTimerTask(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(NewsTimerTask.class);
|
||||
_mgr = (ConsoleUpdateManager) _context.updateManager();
|
||||
ctx.simpleScheduler().addPeriodicEvent(this,
|
||||
INITIAL_DELAY + _context.random().nextLong(INITIAL_DELAY),
|
||||
RUN_DELAY);
|
||||
// UpdateManager calls NewsFetcher to check the existing news at startup
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
if (shouldFetchNews()) {
|
||||
fetchNews();
|
||||
if (shouldFetchUnsigned())
|
||||
fetchUnsignedHead();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldFetchNews() {
|
||||
if (_context.router().gracefulShutdownInProgress())
|
||||
return false;
|
||||
if (NewsHelper.isUpdateInProgress())
|
||||
return false;
|
||||
long lastFetch = NewsHelper.lastChecked(_context);
|
||||
String freq = _context.getProperty(ConfigUpdateHandler.PROP_REFRESH_FREQUENCY,
|
||||
ConfigUpdateHandler.DEFAULT_REFRESH_FREQUENCY);
|
||||
try {
|
||||
long ms = Long.parseLong(freq);
|
||||
if (ms <= 0)
|
||||
return false;
|
||||
|
||||
if (lastFetch + ms < _context.clock().now()) {
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Last fetched " + DataHelper.formatDuration(_context.clock().now() - lastFetch) + " ago");
|
||||
return false;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Invalid refresh frequency: " + freq);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchNews() {
|
||||
_mgr.check(NEWS, "");
|
||||
}
|
||||
|
||||
private boolean shouldFetchUnsigned() {
|
||||
String url = _context.getProperty(ConfigUpdateHandler.PROP_ZIP_URL);
|
||||
return url != null && url.length() > 0 &&
|
||||
_context.getBooleanProperty(ConfigUpdateHandler.PROP_UPDATE_UNSIGNED) &&
|
||||
!NewsHelper.dontInstall(_context);
|
||||
}
|
||||
|
||||
/**
|
||||
* HEAD the update url, and if the last-mod time is newer than the last update we
|
||||
* downloaded, as stored in the properties, then we download it using eepget.
|
||||
*/
|
||||
private void fetchUnsignedHead() {
|
||||
_mgr.check(ROUTER_UNSIGNED, "");
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.web.ConfigUpdateHandler;
|
||||
import net.i2p.update.*;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.PartialEepGet;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Moved from web/ and turned into an UpdateTask.
|
||||
*
|
||||
* @since 0.7.12
|
||||
*/
|
||||
class PluginUpdateChecker extends UpdateRunner {
|
||||
private final ByteArrayOutputStream _baos;
|
||||
private final String _appName;
|
||||
private final String _oldVersion;
|
||||
|
||||
public PluginUpdateChecker(RouterContext ctx, List<URI> uris, String appName, String oldVersion ) {
|
||||
super(ctx, uris);
|
||||
_baos = new ByteArrayOutputStream(TrustedUpdate.HEADER_BYTES);
|
||||
if (!uris.isEmpty())
|
||||
_currentURI = uris.get(0);
|
||||
_appName = appName;
|
||||
_oldVersion = oldVersion;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public UpdateType getType() { return UpdateType.PLUGIN; }
|
||||
|
||||
@Override
|
||||
protected void update() {
|
||||
// must be set for super
|
||||
_isPartial = true;
|
||||
updateStatus("<b>" + _("Checking for update of plugin {0}", _appName) + "</b>");
|
||||
// use the same settings as for updater
|
||||
// always proxy, or else FIXME
|
||||
//boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
|
||||
_baos.reset();
|
||||
try {
|
||||
_get = new PartialEepGet(_context, proxyHost, proxyPort, _baos, _currentURI.toString(), TrustedUpdate.HEADER_BYTES);
|
||||
_get.addStatusListener(this);
|
||||
_get.fetch(CONNECT_TIMEOUT);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error checking update for plugin", t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
// super sets _newVersion if newer
|
||||
boolean newer = _newVersion != null;
|
||||
if (newer) {
|
||||
_mgr.notifyVersionAvailable(this, _currentURI, UpdateType.PLUGIN, _appName, UpdateMethod.HTTP,
|
||||
_urls, _newVersion, _oldVersion);
|
||||
}
|
||||
_mgr.notifyCheckComplete(this, newer, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
File f = new File(_updateFile);
|
||||
f.delete();
|
||||
_mgr.notifyCheckComplete(this, false, false);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.web.PluginStarter;
|
||||
import net.i2p.update.*;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.PartialEepGet;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
/**
|
||||
* Check for or download 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.
|
||||
*
|
||||
* Moved from web/ and turned into an Updater.
|
||||
*
|
||||
* @since 0.7.12
|
||||
* @author zzz
|
||||
*/
|
||||
class PluginUpdateHandler implements Updater {
|
||||
private final RouterContext _context;
|
||||
|
||||
public PluginUpdateHandler(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
/** check a single plugin */
|
||||
@Override
|
||||
public UpdateTask check(UpdateType type, UpdateMethod method,
|
||||
String appName, String currentVersion, long maxTime) {
|
||||
if ((type != UpdateType.PLUGIN) ||
|
||||
method != UpdateMethod.HTTP || appName.length() <= 0)
|
||||
return null;
|
||||
|
||||
Properties props = PluginStarter.pluginProperties(_context, appName);
|
||||
String oldVersion = props.getProperty("version");
|
||||
String xpi2pURL = props.getProperty("updateURL");
|
||||
List<URI> updateSources = null;
|
||||
if (xpi2pURL != null) {
|
||||
try {
|
||||
updateSources = Collections.singletonList(new URI(xpi2pURL));
|
||||
} catch (URISyntaxException use) {}
|
||||
}
|
||||
|
||||
if (oldVersion == null || updateSources == null) {
|
||||
//updateStatus("<b>" + _("Cannot check, plugin {0} is not installed", appName) + "</b>");
|
||||
return null;
|
||||
}
|
||||
|
||||
UpdateRunner update = new PluginUpdateChecker(_context, updateSources, appName, oldVersion);
|
||||
update.start();
|
||||
return update;
|
||||
}
|
||||
|
||||
/** download a single plugin */
|
||||
@Override
|
||||
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
|
||||
String appName, String newVersion, long maxTime) {
|
||||
if ((type != UpdateType.PLUGIN && type != UpdateType.PLUGIN_INSTALL) ||
|
||||
method != UpdateMethod.HTTP || updateSources.isEmpty())
|
||||
return null;
|
||||
Properties props = PluginStarter.pluginProperties(_context, appName);
|
||||
String oldVersion = props.getProperty("version");
|
||||
String xpi2pURL = props.getProperty("updateURL");
|
||||
if (oldVersion == null || xpi2pURL == null) {
|
||||
//updateStatus("<b>" + _("Cannot check, plugin {0} is not installed", appName) + "</b>");
|
||||
return null;
|
||||
}
|
||||
|
||||
UpdateRunner update = new PluginUpdateRunner(_context, updateSources, appName, oldVersion);
|
||||
update.start();
|
||||
return update;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package net.i2p.router.web;
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
@ -9,6 +11,12 @@ import net.i2p.CoreVersion;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.web.ConfigClientsHelper;
|
||||
import net.i2p.router.web.ConfigUpdateHandler;
|
||||
import net.i2p.router.web.LogsHelper;
|
||||
import net.i2p.router.web.Messages;
|
||||
import net.i2p.router.web.PluginStarter;
|
||||
import net.i2p.update.*;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
@ -19,107 +27,55 @@ import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
|
||||
/**
|
||||
* Download and install a plugin.
|
||||
* 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.
|
||||
* Unlike for router updates, we need not have the public key
|
||||
* for the signature in advance.
|
||||
*
|
||||
* The zip file must have a standard directory layout, with
|
||||
* a plugin.config file at the top level.
|
||||
* The config file contains properties for the package name, version,
|
||||
* signing public key, and other settings.
|
||||
* The zip file will typically contain a webapps/ or lib/ dir,
|
||||
* and a webapps.config and/or clients.config 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.
|
||||
*
|
||||
* @since 0.7.12
|
||||
* @author zzz
|
||||
* Moved from web/ and turned into an UpdateTask.
|
||||
*
|
||||
* @since 0.9.2 moved from PluginUpdateHandler
|
||||
*/
|
||||
public class PluginUpdateHandler extends UpdateHandler {
|
||||
private static PluginUpdateRunner _pluginUpdateRunner;
|
||||
private String _xpi2pURL;
|
||||
private String _appStatus;
|
||||
private volatile boolean _updated;
|
||||
class PluginUpdateRunner extends UpdateRunner {
|
||||
|
||||
private String _appName;
|
||||
private final String _oldVersion;
|
||||
private final URI _uri;
|
||||
private final String _xpi2pURL;
|
||||
private boolean _updated;
|
||||
|
||||
private static final String XPI2P = "app.xpi2p";
|
||||
private static final String ZIP = XPI2P + ".zip";
|
||||
public static final String PLUGIN_DIR = "plugins";
|
||||
public static final String PLUGIN_DIR = PluginStarter.PLUGIN_DIR;
|
||||
|
||||
private static PluginUpdateHandler _instance;
|
||||
public static final synchronized PluginUpdateHandler getInstance(RouterContext ctx) {
|
||||
if (_instance != null)
|
||||
return _instance;
|
||||
_instance = new PluginUpdateHandler(ctx);
|
||||
return _instance;
|
||||
public PluginUpdateRunner(RouterContext ctx, List<URI> uris, String appName, String oldVersion ) {
|
||||
super(ctx, uris);
|
||||
if (uris.isEmpty())
|
||||
_uri = null;
|
||||
else
|
||||
_uri = uris.get(0);
|
||||
_xpi2pURL = _uri.toString();
|
||||
_appName = appName;
|
||||
_oldVersion = oldVersion;
|
||||
}
|
||||
|
||||
private PluginUpdateHandler(RouterContext ctx) {
|
||||
super(ctx);
|
||||
_appStatus = "";
|
||||
}
|
||||
|
||||
public void update(String xpi2pURL) {
|
||||
// don't block waiting for the other one to finish
|
||||
if ("true".equals(System.getProperty(PROP_UPDATE_IN_PROGRESS))) {
|
||||
_log.error("Update already running");
|
||||
return;
|
||||
}
|
||||
synchronized (UpdateHandler.class) {
|
||||
if (_pluginUpdateRunner == null)
|
||||
_pluginUpdateRunner = new PluginUpdateRunner(_xpi2pURL);
|
||||
if (_pluginUpdateRunner.isRunning())
|
||||
return;
|
||||
_xpi2pURL = xpi2pURL;
|
||||
_updateFile = (new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + XPI2P)).getAbsolutePath();
|
||||
System.setProperty(PROP_UPDATE_IN_PROGRESS, "true");
|
||||
I2PAppThread update = new I2PAppThread(_pluginUpdateRunner, "AppDownload");
|
||||
update.start();
|
||||
}
|
||||
}
|
||||
|
||||
public String getAppStatus() {
|
||||
return _appStatus;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return _pluginUpdateRunner != null && _pluginUpdateRunner.isRunning();
|
||||
@Override
|
||||
public UpdateType getType() {
|
||||
return _oldVersion.equals("") ? UpdateType.PLUGIN_INSTALL : UpdateType.PLUGIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
// FIXME
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @since 0.8.13 */
|
||||
public boolean wasUpdateSuccessful() {
|
||||
return _updated;
|
||||
}
|
||||
|
||||
private void scheduleStatusClean(String msg) {
|
||||
_context.simpleScheduler().addEvent(new Cleaner(msg), 20*60*1000);
|
||||
}
|
||||
|
||||
private class Cleaner implements SimpleTimer.TimedEvent {
|
||||
private final String _msg;
|
||||
public Cleaner(String msg) {
|
||||
_msg = msg;
|
||||
}
|
||||
public void timeReached() {
|
||||
if (_msg.equals(getStatus()))
|
||||
updateStatus("");
|
||||
}
|
||||
}
|
||||
|
||||
public class PluginUpdateRunner extends UpdateRunner implements Runnable, EepGet.StatusListener {
|
||||
|
||||
public PluginUpdateRunner(String url) {
|
||||
super();
|
||||
}
|
||||
public URI getURI() { return _uri; }
|
||||
|
||||
@Override
|
||||
protected void update() {
|
||||
|
||||
_updated = false;
|
||||
if(_xpi2pURL.startsWith("file://")) {
|
||||
updateStatus("<b>" + _("Attempting to install from file {0}", _xpi2pURL) + "</b>");
|
||||
@ -139,7 +95,7 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
} else {
|
||||
updateStatus("<b>" + _("Downloading plugin from {0}", _xpi2pURL) + "</b>");
|
||||
// use the same settings as for updater
|
||||
boolean shouldProxy = Boolean.parseBoolean(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY));
|
||||
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
|
||||
try {
|
||||
@ -156,21 +112,6 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
buf.append("<b>").append(_("Downloading plugin")).append(' ');
|
||||
double pct = ((double)alreadyTransferred + (double)currentWrite) /
|
||||
((double)alreadyTransferred + (double)currentWrite + bytesRemaining);
|
||||
synchronized (_pct) {
|
||||
buf.append(_pct.format(pct));
|
||||
}
|
||||
buf.append(": ");
|
||||
buf.append(_("{0}B transferred", DataHelper.formatSize2(currentWrite + alreadyTransferred)));
|
||||
buf.append("</b>");
|
||||
updateStatus(buf.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
boolean update = false;
|
||||
@ -313,7 +254,7 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
boolean wasRunning = false;
|
||||
File destDir = new SecureDirectory(appDir, appName);
|
||||
if (destDir.exists()) {
|
||||
if (Boolean.parseBoolean(props.getProperty("install-only"))) {
|
||||
if (Boolean.valueOf(props.getProperty("install-only")).booleanValue()) {
|
||||
to.delete();
|
||||
statusDone("<b>" + _("Downloaded plugin is for new installs only, but the plugin is already installed", url) + "</b>");
|
||||
return;
|
||||
@ -374,7 +315,7 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
return;
|
||||
}
|
||||
// do we defer extraction and installation?
|
||||
if (Boolean.parseBoolean(props.getProperty("router-restart-required"))) {
|
||||
if (Boolean.valueOf(props.getProperty("router-restart-required")).booleanValue()) {
|
||||
// Yup!
|
||||
try {
|
||||
if(!FileUtil.copy(to, (new SecureFile( new SecureFile(appDir.getCanonicalPath() +"/" + appName +"/"+ ZIP).getCanonicalPath())) , true, true)) {
|
||||
@ -405,7 +346,7 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
}
|
||||
update = true;
|
||||
} else {
|
||||
if (Boolean.parseBoolean(props.getProperty("update-only"))) {
|
||||
if (Boolean.valueOf(props.getProperty("update-only")).booleanValue()) {
|
||||
to.delete();
|
||||
statusDone("<b>" + _("Plugin is for upgrades only, but the plugin is not installed") + "</b>");
|
||||
return;
|
||||
@ -426,7 +367,7 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
_updated = true;
|
||||
to.delete();
|
||||
// install != update. Changing the user's settings like this is probabbly a bad idea.
|
||||
if (Boolean.parseBoolean( props.getProperty("dont-start-at-install"))) {
|
||||
if (Boolean.valueOf( props.getProperty("dont-start-at-install")).booleanValue()) {
|
||||
statusDone("<b>" + _("Plugin {0} installed", appName) + "</b>");
|
||||
if(!update) {
|
||||
Properties pluginProps = PluginStarter.pluginProperties();
|
||||
@ -468,15 +409,7 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
|
||||
private void statusDone(String msg) {
|
||||
updateStatus(msg);
|
||||
scheduleStatusClean(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStatus(String s) {
|
||||
super.updateStatus(s);
|
||||
_appStatus = s;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,94 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.util.RFC822Date;
|
||||
import net.i2p.router.web.ConfigUpdateHandler;
|
||||
import net.i2p.router.web.Messages;
|
||||
import net.i2p.update.*;
|
||||
import net.i2p.util.EepHead;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Does a simple EepHead to get the last-modified header.
|
||||
* Moved from NewsFetcher and turned into an UpdateTask.
|
||||
*
|
||||
* Overrides UpdateRunner for convenience, does not use super's Eepget StatusListener
|
||||
*
|
||||
* @since 0.9.2
|
||||
*/
|
||||
class UnsignedUpdateChecker extends UpdateRunner {
|
||||
private final long _ms;
|
||||
private boolean _unsignedUpdateAvailable;
|
||||
|
||||
protected static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
|
||||
|
||||
public UnsignedUpdateChecker(RouterContext ctx, List<URI> uris, long lastUpdateTime) {
|
||||
super(ctx, uris);
|
||||
_ms = lastUpdateTime;
|
||||
}
|
||||
|
||||
//////// begin UpdateTask methods
|
||||
|
||||
@Override
|
||||
public UpdateType getType() { return UpdateType.ROUTER_UNSIGNED; }
|
||||
|
||||
//////// end UpdateTask methods
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
_isRunning = true;
|
||||
boolean success = false;
|
||||
try {
|
||||
success = fetchUnsignedHead();
|
||||
} finally {
|
||||
_isRunning = false;
|
||||
}
|
||||
_mgr.notifyCheckComplete(this, _unsignedUpdateAvailable, success);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* HEAD the update url, and if the last-mod time is newer than the last update we
|
||||
* downloaded, as stored in the properties, then we download it using eepget.
|
||||
*/
|
||||
private boolean fetchUnsignedHead() {
|
||||
if (_urls.isEmpty())
|
||||
return false;
|
||||
_currentURI = _urls.get(0);
|
||||
String url = _currentURI.toString();
|
||||
// assume always proxied for now
|
||||
//boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT_INT);
|
||||
|
||||
try {
|
||||
EepHead get = new EepHead(_context, proxyHost, proxyPort, 0, url);
|
||||
if (get.fetch()) {
|
||||
String lastmod = get.getLastModified();
|
||||
if (lastmod != null) {
|
||||
long modtime = RFC822Date.parse822Date(lastmod);
|
||||
if (modtime <= 0) return false;
|
||||
if (_ms <= 0) return false;
|
||||
if (modtime > _ms) {
|
||||
_unsignedUpdateAvailable = true;
|
||||
// '07-Jul 21:09 UTC' with month name in the system locale
|
||||
String unsignedUpdateVersion = (new SimpleDateFormat("dd-MMM HH:mm")).format(new Date(modtime)) + " UTC";
|
||||
_mgr.notifyVersionAvailable(this, _urls.get(0), getType(), "", getMethod(), _urls,
|
||||
unsignedUpdateVersion, "");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error fetching the unsigned update", t);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.util.RFC822Date;
|
||||
import net.i2p.router.web.ConfigUpdateHandler;
|
||||
import net.i2p.router.web.NewsHelper;
|
||||
import net.i2p.update.*;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Handles the request to update the router by firing off an
|
||||
* {@link net.i2p.util.EepGet} call to download the latest unsigned zip file
|
||||
* and displaying the status to anyone who asks.
|
||||
* </p>
|
||||
* <p>After the download completes the signed update file is copied to the
|
||||
* router directory, and if configured the router is restarted to complete
|
||||
* the update process.
|
||||
* </p>
|
||||
*/
|
||||
class UnsignedUpdateHandler implements Updater {
|
||||
private final RouterContext _context;
|
||||
|
||||
public UnsignedUpdateHandler(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currentVersion ignored, we use time stored in a property
|
||||
*/
|
||||
@Override
|
||||
public UpdateTask check(UpdateType type, UpdateMethod method,
|
||||
String id, String currentVersion, long maxTime) {
|
||||
if (type != UpdateType.ROUTER_UNSIGNED || method != UpdateMethod.HTTP)
|
||||
return null;
|
||||
|
||||
String url = _context.getProperty(ConfigUpdateHandler.PROP_ZIP_URL);
|
||||
if (url == null)
|
||||
return null;
|
||||
|
||||
List<URI> updateSources;
|
||||
try {
|
||||
updateSources = Collections.singletonList(new URI(url));
|
||||
} catch (URISyntaxException use) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String lastUpdate = _context.getProperty(NewsHelper.PROP_LAST_UPDATE_TIME);
|
||||
if (lastUpdate == null) {
|
||||
// we don't know what version you have, so stamp it with the current time,
|
||||
// and we'll look for something newer next time around.
|
||||
_context.router().saveConfig(NewsHelper.PROP_LAST_UPDATE_TIME,
|
||||
Long.toString(_context.clock().now()));
|
||||
return null;
|
||||
}
|
||||
long ms = 0;
|
||||
try {
|
||||
ms = Long.parseLong(lastUpdate);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
if (ms <= 0) {
|
||||
// we don't know what version you have, so stamp it with the current time,
|
||||
// and we'll look for something newer next time around.
|
||||
_context.router().saveConfig(NewsHelper.PROP_LAST_UPDATE_TIME,
|
||||
Long.toString(_context.clock().now()));
|
||||
return null;
|
||||
}
|
||||
|
||||
UpdateRunner update = new UnsignedUpdateChecker(_context, updateSources, ms);
|
||||
update.start();
|
||||
return update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a download and return a handle to the download task.
|
||||
* Should not block.
|
||||
*
|
||||
* @param id plugin name or ignored
|
||||
* @param maxTime how long you have
|
||||
* @return active task or null if unable to download
|
||||
*/
|
||||
@Override
|
||||
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
|
||||
String id, String newVersion, long maxTime) {
|
||||
if (type != UpdateType.ROUTER_UNSIGNED || method != UpdateMethod.HTTP || updateSources.isEmpty())
|
||||
return null;
|
||||
UpdateRunner update = new UnsignedUpdateRunner(_context, updateSources);
|
||||
update.start();
|
||||
return update;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.util.RFC822Date;
|
||||
import net.i2p.router.web.ConfigUpdateHandler;
|
||||
import net.i2p.update.*;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Eepget the .zip file to the temp dir, then notify.r
|
||||
* Moved from UnsignedUpdateHandler and turned into an UpdateTask.
|
||||
*
|
||||
* @since 0.9.2
|
||||
*/
|
||||
class UnsignedUpdateRunner extends UpdateRunner {
|
||||
|
||||
public UnsignedUpdateRunner(RouterContext ctx, List<URI> uris) {
|
||||
super(ctx, uris);
|
||||
if (!uris.isEmpty())
|
||||
_currentURI = uris.get(0);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public UpdateType getType() { return UpdateType.ROUTER_UNSIGNED; }
|
||||
|
||||
|
||||
/** Get the file */
|
||||
@Override
|
||||
protected void update() {
|
||||
String zipURL = _currentURI.toString();
|
||||
updateStatus("<b>" + _("Updating") + "</b>");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Starting unsigned update URL: " + zipURL);
|
||||
// always proxy for now
|
||||
//boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
|
||||
try {
|
||||
// 40 retries!!
|
||||
_get = new EepGet(_context, proxyHost, proxyPort, 40, _updateFile, zipURL, false);
|
||||
_get.addStatusListener(UnsignedUpdateRunner.this);
|
||||
_get.fetch(CONNECT_TIMEOUT, -1, INACTIVITY_TIMEOUT);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error updating", t);
|
||||
}
|
||||
}
|
||||
|
||||
/** eepget listener callback Overrides */
|
||||
@Override
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
String lastmod = _get.getLastModified();
|
||||
File tmp = new File(_updateFile);
|
||||
/////// FIXME RFC822 or long?
|
||||
if (_mgr.notifyComplete(this, lastmod, tmp))
|
||||
this.done = true;
|
||||
else
|
||||
tmp.delete(); // corrupt
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
import net.i2p.router.util.RFC822Date;
|
||||
import net.i2p.update.*;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PartialEepGet;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
/**
|
||||
* <p>Handles the request to update the router by firing one or more
|
||||
* {@link net.i2p.util.EepGet} calls to download the latest signed update file
|
||||
* and displaying the status to anyone who asks.
|
||||
* </p>
|
||||
* <p>After the download completes the signed update file is verified with
|
||||
* {@link net.i2p.crypto.TrustedUpdate}, and if it's authentic the payload
|
||||
* of the signed update file is unpacked and the router is restarted to complete
|
||||
* the update process.
|
||||
* </p>
|
||||
*
|
||||
* This does not do any checking, that is handled by the NewsFetcher.
|
||||
*/
|
||||
class UpdateHandler implements Updater {
|
||||
protected final RouterContext _context;
|
||||
|
||||
public UpdateHandler(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
/** Can't check, the NewsHandler does that */
|
||||
public UpdateTask check(UpdateType type, UpdateMethod method,
|
||||
String id, String currentVersion, long maxTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a download and return a handle to the download task.
|
||||
* Should not block.
|
||||
*
|
||||
* @param id plugin name or ignored
|
||||
* @param maxTime how long you have
|
||||
* @return active task or null if unable to download
|
||||
*/
|
||||
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
|
||||
String id, String newVersion, long maxTime) {
|
||||
if ((type != UpdateType.ROUTER_SIGNED && type != UpdateType.ROUTER_SIGNED_PACK200) ||
|
||||
method != UpdateMethod.HTTP || updateSources.isEmpty())
|
||||
return null;
|
||||
UpdateRunner update = new UpdateRunner(_context, updateSources);
|
||||
update.start();
|
||||
return update;
|
||||
}
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
import net.i2p.router.web.ConfigUpdateHandler;
|
||||
import net.i2p.router.web.Messages;
|
||||
import net.i2p.update.*;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PartialEepGet;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
/**
|
||||
* The downloader for router signed updates,
|
||||
* and the base class for all the other Checkers and Runners.
|
||||
*
|
||||
* @since 0.9.2 moved from UpdateHandler
|
||||
*
|
||||
*/
|
||||
class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusListener {
|
||||
protected final RouterContext _context;
|
||||
protected final Log _log;
|
||||
protected final ConsoleUpdateManager _mgr;
|
||||
protected final List<URI> _urls;
|
||||
protected final String _updateFile;
|
||||
protected volatile boolean _isRunning;
|
||||
protected boolean done;
|
||||
protected EepGet _get;
|
||||
/** tells the listeners what mode we are in - set to true in extending classes for checks */
|
||||
protected boolean _isPartial;
|
||||
/** set by the listeners on completion */
|
||||
protected String _newVersion;
|
||||
private ByteArrayOutputStream _baos;
|
||||
protected URI _currentURI;
|
||||
|
||||
private static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
|
||||
|
||||
protected static final long CONNECT_TIMEOUT = 55*1000;
|
||||
protected static final long INACTIVITY_TIMEOUT = 5*60*1000;
|
||||
protected static final long NOPROXY_INACTIVITY_TIMEOUT = 60*1000;
|
||||
|
||||
public UpdateRunner(RouterContext ctx, List<URI> uris) {
|
||||
super("Update Runner");
|
||||
setDaemon(true);
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(getClass());
|
||||
_mgr = (ConsoleUpdateManager) ctx.updateManager();
|
||||
_urls = uris;
|
||||
_updateFile = (new File(ctx.getTempDir(), "update" + ctx.random().nextInt() + ".tmp")).getAbsolutePath();
|
||||
}
|
||||
|
||||
//////// begin UpdateTask methods
|
||||
|
||||
public boolean isRunning() { return _isRunning; }
|
||||
|
||||
public void shutdown() {
|
||||
_isRunning = false;
|
||||
interrupt();
|
||||
}
|
||||
|
||||
public UpdateType getType() { return UpdateType.ROUTER_SIGNED; }
|
||||
|
||||
public UpdateMethod getMethod() { return UpdateMethod.HTTP; }
|
||||
|
||||
public URI getURI() { return _currentURI; }
|
||||
|
||||
public String getID() { return ""; }
|
||||
|
||||
//////// end UpdateTask methods
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
_isRunning = true;
|
||||
try {
|
||||
update();
|
||||
} finally {
|
||||
_isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected void update() {
|
||||
// Do a PartialEepGet on the selected URL, check for version we expect,
|
||||
// and loop if it isn't what we want.
|
||||
// This will allows us to do a release without waiting for the last host to install the update.
|
||||
// Alternative: In bytesTransferred(), Check the data in the output file after
|
||||
// we've received at least 56 bytes. Need a cancel() method in EepGet ?
|
||||
|
||||
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
|
||||
|
||||
if (_urls.isEmpty()) {
|
||||
// not likely, don't bother translating
|
||||
updateStatus("<b>Update source list is empty, cannot download update</b>");
|
||||
_log.log(Log.CRIT, "Update source list is empty - cannot download update");
|
||||
_mgr.notifyTaskFailed(this, "", null);
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = null;
|
||||
if (shouldProxy)
|
||||
baos = new ByteArrayOutputStream(TrustedUpdate.HEADER_BYTES);
|
||||
for (URI uri : _urls) {
|
||||
_currentURI = uri;
|
||||
String updateURL = uri.toString();
|
||||
updateStatus("<b>" + _("Updating from {0}", linkify(updateURL)) + "</b>");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Selected update URL: " + updateURL);
|
||||
|
||||
// Check the first 56 bytes for the version
|
||||
if (shouldProxy) {
|
||||
_isPartial = true;
|
||||
baos.reset();
|
||||
try {
|
||||
// no retries
|
||||
_get = new PartialEepGet(_context, proxyHost, proxyPort, baos, updateURL, TrustedUpdate.HEADER_BYTES);
|
||||
_get.addStatusListener(UpdateRunner.this);
|
||||
_get.fetch(CONNECT_TIMEOUT);
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
_isPartial = false;
|
||||
if (_newVersion == null)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now get the whole thing
|
||||
try {
|
||||
if (shouldProxy)
|
||||
// 40 retries!!
|
||||
_get = new EepGet(_context, proxyHost, proxyPort, 40, _updateFile, updateURL, false);
|
||||
else
|
||||
_get = new EepGet(_context, 1, _updateFile, updateURL, false);
|
||||
_get.addStatusListener(UpdateRunner.this);
|
||||
_get.fetch(CONNECT_TIMEOUT, -1, shouldProxy ? INACTIVITY_TIMEOUT : NOPROXY_INACTIVITY_TIMEOUT);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error updating", t);
|
||||
}
|
||||
if (this.done)
|
||||
break;
|
||||
}
|
||||
(new File(_updateFile)).delete();
|
||||
if (!this.done)
|
||||
_mgr.notifyTaskFailed(this, "", null);
|
||||
}
|
||||
|
||||
// EepGet Listeners below.
|
||||
// We use the same for both the partial and the full EepGet,
|
||||
// with a couple of adjustments depending on which mode.
|
||||
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Attempt failed on " + url, cause);
|
||||
// ignored
|
||||
}
|
||||
|
||||
/** subclasses should override */
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
if (_isPartial)
|
||||
return;
|
||||
long d = currentWrite + bytesTransferred;
|
||||
String status = "<b>" + _("Updating") + "</b>";
|
||||
_mgr.notifyProgress(this, status, d, d + bytesRemaining);
|
||||
}
|
||||
|
||||
/** subclasses should override */
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
if (_isPartial) {
|
||||
// Compare version with what we have now
|
||||
String newVersion = TrustedUpdate.getVersionString(new ByteArrayInputStream(_baos.toByteArray()));
|
||||
boolean newer = (new VersionComparator()).compare(newVersion, RouterVersion.VERSION) > 0;
|
||||
if (newer) {
|
||||
_newVersion = newVersion;
|
||||
} else {
|
||||
updateStatus("<b>" + _("No new version found at {0}", linkify(url)) + "</b>");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Found old version \"" + newVersion + "\" at " + url);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
File tmp = new File(_updateFile);
|
||||
if (_mgr.notifyComplete(this, _newVersion, tmp))
|
||||
this.done = true;
|
||||
else
|
||||
tmp.delete(); // corrupt
|
||||
}
|
||||
|
||||
/** subclasses should override */
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
// don't display bytesTransferred as it is meaningless
|
||||
_log.error("Update from " + url + " did not download completely (" +
|
||||
bytesRemaining + " remaining after " + currentAttempt + " tries)");
|
||||
updateStatus("<b>" + _("Transfer failed from {0}", linkify(url)) + "</b>");
|
||||
}
|
||||
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||
|
||||
public void attempting(String url) {}
|
||||
|
||||
protected void updateStatus(String s) {
|
||||
_mgr.notifyProgress(this, s);
|
||||
}
|
||||
|
||||
protected static String linkify(String url) {
|
||||
return ConsoleUpdateManager.linkify(url);
|
||||
}
|
||||
|
||||
/** translate a string */
|
||||
protected String _(String s) {
|
||||
return Messages.getString(s, _context);
|
||||
}
|
||||
|
||||
/**
|
||||
* translate a string with a parameter
|
||||
*/
|
||||
protected String _(String s, Object o) {
|
||||
return Messages.getString(s, o, _context);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Classes to implement the update process.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
@ -59,7 +59,7 @@ public class CSSHelper extends HelperBase {
|
||||
public void setNews(String val) {
|
||||
// Protected with nonce in css.jsi
|
||||
if (val != null)
|
||||
NewsFetcher.getInstance(_context).showNews(val.equals("1"));
|
||||
NewsHelper.showNews(_context, val.equals("1"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -12,6 +14,8 @@ import java.util.Set;
|
||||
import net.i2p.router.client.ClientManagerFacadeImpl;
|
||||
import net.i2p.router.startup.ClientAppConfig;
|
||||
import net.i2p.router.startup.LoadClientAppsJob;
|
||||
import net.i2p.router.update.ConsoleUpdateManager;
|
||||
import static net.i2p.update.UpdateType.*;
|
||||
|
||||
import org.mortbay.jetty.handler.ContextHandlerCollection;
|
||||
|
||||
@ -333,7 +337,7 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
|
||||
/** @since 0.8.13 */
|
||||
private void updateAllPlugins() {
|
||||
if ("true".equals(System.getProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS))) {
|
||||
if (NewsHelper.isAnyUpdateInProgress()) {
|
||||
addFormError(_("Plugin or update download already in progress."));
|
||||
return;
|
||||
}
|
||||
@ -346,17 +350,26 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
}
|
||||
|
||||
private void installPlugin(String url) {
|
||||
if ("true".equals(System.getProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS))) {
|
||||
ConsoleUpdateManager mgr = (ConsoleUpdateManager) _context.updateManager();
|
||||
if (mgr == null) {
|
||||
addFormError("Update manager not registered, cannot install");
|
||||
return;
|
||||
}
|
||||
if (mgr.isUpdateInProgress()) {
|
||||
addFormError(_("Plugin or update download already in progress."));
|
||||
return;
|
||||
}
|
||||
PluginUpdateHandler puh = PluginUpdateHandler.getInstance(_context);
|
||||
if (puh.isRunning()) {
|
||||
addFormError(_("Plugin or update download already in progress."));
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(url);
|
||||
} catch (URISyntaxException use) {
|
||||
addFormError(_("Bad URL {0}", url));
|
||||
return;
|
||||
}
|
||||
puh.update(url);
|
||||
addFormNotice(_("Downloading plugin from {0}", url));
|
||||
if (mgr.installPlugin(uri))
|
||||
addFormNotice(_("Downloading plugin from {0}", url));
|
||||
else
|
||||
addFormError("Cannot install, check logs");
|
||||
// So that update() will post a status to the summary bar before we reload
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
@ -364,16 +377,12 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
}
|
||||
|
||||
private void checkPlugin(String app) {
|
||||
if ("true".equals(System.getProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS))) {
|
||||
addFormError(_("Plugin or update download already in progress."));
|
||||
ConsoleUpdateManager mgr = (ConsoleUpdateManager) _context.updateManager();
|
||||
if (mgr == null) {
|
||||
addFormError("Update manager not registered, cannot check");
|
||||
return;
|
||||
}
|
||||
PluginUpdateChecker puc = PluginUpdateChecker.getInstance(_context);
|
||||
if (puc.isRunning()) {
|
||||
addFormError(_("Plugin or update download already in progress."));
|
||||
return;
|
||||
}
|
||||
puc.update(app);
|
||||
mgr.check(PLUGIN, app);
|
||||
addFormNotice(_("Checking plugin {0} for updates", app));
|
||||
// So that update() will post a status to the summary bar before we reload
|
||||
try {
|
||||
|
@ -293,7 +293,7 @@ public class ConfigClientsHelper extends HelperBase {
|
||||
* Like in DataHelper but doesn't convert null to ""
|
||||
* There's a lot worse things a plugin could do but...
|
||||
*/
|
||||
static String stripHTML(Properties props, String key) {
|
||||
public static String stripHTML(Properties props, String key) {
|
||||
String orig = props.getProperty(key);
|
||||
if (orig == null) return null;
|
||||
String t1 = orig.replace('<', ' ');
|
||||
|
@ -6,6 +6,8 @@ import java.util.Map;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.update.ConsoleUpdateManager;
|
||||
import static net.i2p.update.UpdateType.*;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.PortMapper;
|
||||
|
||||
@ -84,7 +86,7 @@ public class ConfigUpdateHandler extends FormHandler {
|
||||
* @return the configured value, else the registered HTTP proxy, else the default
|
||||
* @since 0.8.13
|
||||
*/
|
||||
static int proxyPort(I2PAppContext ctx) {
|
||||
public static int proxyPort(I2PAppContext ctx) {
|
||||
return ctx.getProperty(PROP_PROXY_PORT,
|
||||
ctx.portMapper().getPort(PortMapper.SVC_HTTP_PROXY, DEFAULT_PROXY_PORT_INT));
|
||||
}
|
||||
@ -94,11 +96,16 @@ public class ConfigUpdateHandler extends FormHandler {
|
||||
if (_action == null)
|
||||
return;
|
||||
if (_action.equals(_("Check for updates"))) {
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(_context);
|
||||
fetcher.fetchNews();
|
||||
if (fetcher.shouldFetchUnsigned())
|
||||
fetcher.fetchUnsignedHead();
|
||||
if (fetcher.updateAvailable() || fetcher.unsignedUpdateAvailable()) {
|
||||
ConsoleUpdateManager mgr = (ConsoleUpdateManager) _context.updateManager();
|
||||
if (mgr == null) {
|
||||
addFormError("Update manager not registered, cannot check");
|
||||
return;
|
||||
}
|
||||
boolean a1 = mgr.checkAvailable(NEWS, 60*1000) != null;
|
||||
boolean a2 = false;
|
||||
if ((!a1) && _updateUnsigned && _zipURL != null && _zipURL.length() > 0)
|
||||
a2 = mgr.checkAvailable(ROUTER_UNSIGNED, 60*1000) != null;
|
||||
if (a1 || a2) {
|
||||
if ( (_updatePolicy == null) || (!_updatePolicy.equals("notify")) )
|
||||
addFormNotice(_("Update available, attempting to download now"));
|
||||
else
|
||||
@ -118,7 +125,8 @@ public class ConfigUpdateHandler extends FormHandler {
|
||||
String oldURL = ConfigUpdateHelper.getNewsURL(_context);
|
||||
if ( (oldURL == null) || (!_newsURL.equals(oldURL)) ) {
|
||||
changes.put(PROP_NEWS_URL, _newsURL);
|
||||
NewsFetcher.getInstance(_context).invalidateNews();
|
||||
// this invalidates the news
|
||||
changes.put(NewsHelper.PROP_LAST_CHECKED, "0");
|
||||
addFormNotice(_("Updating news URL to {0}", _newsURL));
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ public class ConfigUpdateHelper extends HelperBase {
|
||||
@Override
|
||||
public void setContextId(String contextId) {
|
||||
super.setContextId(contextId);
|
||||
_dontInstall = NewsFetcher.getInstance(_context).dontInstall();
|
||||
_dontInstall = NewsHelper.dontInstall(_context);
|
||||
}
|
||||
|
||||
public boolean canInstall() {
|
||||
@ -159,6 +159,6 @@ public class ConfigUpdateHelper extends HelperBase {
|
||||
}
|
||||
|
||||
public String getNewsStatus() {
|
||||
return NewsFetcher.getInstance(_context).status();
|
||||
return NewsHelper.status(_context);
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ public class FileDumpHelper extends HelperBase {
|
||||
dumpDir(buf, dir, ".war");
|
||||
|
||||
// plugins
|
||||
File pluginDir = new File(_context.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR);
|
||||
File pluginDir = new File(_context.getConfigDir(), PluginStarter.PLUGIN_DIR);
|
||||
File[] files = pluginDir.listFiles();
|
||||
if (files != null) {
|
||||
Arrays.sort(files);
|
||||
|
@ -20,7 +20,7 @@ public class LogsHelper extends HelperBase {
|
||||
}
|
||||
|
||||
/** @since 0.8.13 */
|
||||
static String jettyVersion() {
|
||||
public static String jettyVersion() {
|
||||
return Server.getVersion();
|
||||
}
|
||||
|
||||
|
@ -1,416 +0,0 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
import net.i2p.router.util.RFC822Date;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.EepHead;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Task to periodically look for updates to the news.xml, and to keep
|
||||
* track of whether that has an announcement for a new version.
|
||||
*/
|
||||
public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
private boolean _updateAvailable;
|
||||
private boolean _unsignedUpdateAvailable;
|
||||
private long _lastFetch;
|
||||
private long _lastUpdated;
|
||||
private String _updateVersion;
|
||||
private String _unsignedUpdateVersion;
|
||||
private String _lastModified;
|
||||
private boolean _invalidated;
|
||||
private final File _newsFile;
|
||||
private final File _tempFile;
|
||||
private static NewsFetcher _instance;
|
||||
private volatile boolean _isRunning;
|
||||
|
||||
//public static final synchronized NewsFetcher getInstance() { return _instance; }
|
||||
public static final synchronized NewsFetcher getInstance(RouterContext ctx) {
|
||||
if (_instance != null)
|
||||
return _instance;
|
||||
_instance = new NewsFetcher(ctx);
|
||||
return _instance;
|
||||
}
|
||||
|
||||
private static final String NEWS_FILE = "docs/news.xml";
|
||||
private static final String TEMP_NEWS_FILE = "news.xml.temp";
|
||||
/** @since 0.7.14 not configurable */
|
||||
private static final String BACKUP_NEWS_URL = "http://www.i2p2.i2p/_static/news/news.xml";
|
||||
private static final String PROP_LAST_CHECKED = "router.newsLastChecked";
|
||||
/** @since 0.8.12 */
|
||||
private static final String PROP_LAST_HIDDEN = "routerconsole.newsLastHidden";
|
||||
|
||||
private NewsFetcher(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(NewsFetcher.class);
|
||||
_instance = this;
|
||||
try {
|
||||
String last = ctx.getProperty(PROP_LAST_CHECKED);
|
||||
if (last != null)
|
||||
_lastFetch = Long.parseLong(last);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
_newsFile = new File(_context.getRouterDir(), NEWS_FILE);
|
||||
_tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
|
||||
updateLastFetched();
|
||||
_updateVersion = "";
|
||||
_isRunning = true;
|
||||
}
|
||||
|
||||
/** @since 0.8.8 */
|
||||
void shutdown() {
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
private void updateLastFetched() {
|
||||
if (_newsFile.exists()) {
|
||||
if (_lastUpdated == 0)
|
||||
_lastUpdated = _newsFile.lastModified();
|
||||
if (_lastFetch == 0)
|
||||
_lastFetch = _lastUpdated;
|
||||
if (_lastModified == null)
|
||||
_lastModified = RFC822Date.to822Date(_lastFetch);
|
||||
} else {
|
||||
_lastUpdated = 0;
|
||||
_lastFetch = 0;
|
||||
_lastModified = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean updateAvailable() { return _updateAvailable; }
|
||||
public String updateVersion() { return _updateVersion; }
|
||||
public boolean unsignedUpdateAvailable() { return _unsignedUpdateAvailable; }
|
||||
public String unsignedUpdateVersion() { return _unsignedUpdateVersion; }
|
||||
|
||||
/**
|
||||
* Is the news newer than the last time it was hidden?
|
||||
* @since 0.8.12
|
||||
*/
|
||||
public boolean shouldShowNews() {
|
||||
if (_lastUpdated <= 0)
|
||||
return true;
|
||||
String h = _context.getProperty(PROP_LAST_HIDDEN);
|
||||
if (h == null)
|
||||
return true;
|
||||
long last = 0;
|
||||
try {
|
||||
last = Long.parseLong(h);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
return _lastUpdated > last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save config with the timestamp of the current news to hide, or 0 to show
|
||||
* @since 0.8.12
|
||||
*/
|
||||
public void showNews(boolean yes) {
|
||||
long stamp = yes ? 0 : _lastUpdated;
|
||||
_context.router().saveConfig(PROP_LAST_HIDDEN, Long.toString(stamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HTML
|
||||
*/
|
||||
public String status() {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
long now = _context.clock().now();
|
||||
buf.append("<i>");
|
||||
if (_lastUpdated > 0) {
|
||||
buf.append(Messages.getString("News last updated {0} ago.",
|
||||
DataHelper.formatDuration2(now - _lastUpdated),
|
||||
_context))
|
||||
.append('\n');
|
||||
}
|
||||
if (_lastFetch > _lastUpdated) {
|
||||
buf.append(Messages.getString("News last checked {0} ago.",
|
||||
DataHelper.formatDuration2(now - _lastFetch),
|
||||
_context));
|
||||
}
|
||||
buf.append("</i>");
|
||||
String consoleNonce = System.getProperty("router.consoleNonce");
|
||||
if (_lastUpdated > 0 && consoleNonce != null) {
|
||||
if (shouldShowNews()) {
|
||||
buf.append(" <a href=\"/?news=0&consoleNonce=").append(consoleNonce).append("\">")
|
||||
.append(Messages.getString("Hide news", _context));
|
||||
} else {
|
||||
buf.append(" <a href=\"/?news=1&consoleNonce=").append(consoleNonce).append("\">")
|
||||
.append(Messages.getString("Show news", _context));
|
||||
}
|
||||
buf.append("</a>");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final long INITIAL_DELAY = 5*60*1000;
|
||||
private static final long RUN_DELAY = 10*60*1000;
|
||||
|
||||
public void run() {
|
||||
try { Thread.sleep(INITIAL_DELAY + _context.random().nextLong(INITIAL_DELAY)); } catch (InterruptedException ie) {}
|
||||
while (_isRunning) {
|
||||
if (!_updateAvailable) checkForUpdates();
|
||||
if (shouldFetchNews()) {
|
||||
fetchNews();
|
||||
if (shouldFetchUnsigned())
|
||||
fetchUnsignedHead();
|
||||
}
|
||||
try { Thread.sleep(RUN_DELAY); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
boolean dontInstall() {
|
||||
File test = new File(_context.getBaseDir(), "history.txt");
|
||||
boolean readonly = ((test.exists() && !test.canWrite()) || (!_context.getBaseDir().canWrite()));
|
||||
boolean disabled = _context.getBooleanProperty(ConfigUpdateHandler.PROP_UPDATE_DISABLED);
|
||||
return readonly || disabled;
|
||||
}
|
||||
|
||||
private boolean shouldInstall() {
|
||||
String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
|
||||
if ("notify".equals(policy) || dontInstall())
|
||||
return false;
|
||||
File zip = new File(_context.getRouterDir(), Router.UPDATE_FILE);
|
||||
return !zip.exists();
|
||||
}
|
||||
|
||||
private boolean shouldFetchNews() {
|
||||
if (_invalidated)
|
||||
return true;
|
||||
updateLastFetched();
|
||||
String freq = _context.getProperty(ConfigUpdateHandler.PROP_REFRESH_FREQUENCY,
|
||||
ConfigUpdateHandler.DEFAULT_REFRESH_FREQUENCY);
|
||||
try {
|
||||
long ms = Long.parseLong(freq);
|
||||
if (ms <= 0)
|
||||
return false;
|
||||
|
||||
if (_lastFetch + ms < _context.clock().now()) {
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Last fetched " + DataHelper.formatDuration(_context.clock().now() - _lastFetch) + " ago");
|
||||
return false;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Invalid refresh frequency: " + freq);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when changing news URLs to force an update next time the timer fires.
|
||||
* @since 0.8.7
|
||||
*/
|
||||
void invalidateNews() {
|
||||
_lastModified = null;
|
||||
_invalidated = true;
|
||||
}
|
||||
|
||||
public void fetchNews() {
|
||||
String newsURL = ConfigUpdateHelper.getNewsURL(_context);
|
||||
boolean shouldProxy = Boolean.parseBoolean(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY));
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
|
||||
if (_tempFile.exists())
|
||||
_tempFile.delete();
|
||||
|
||||
try {
|
||||
EepGet get = null;
|
||||
if (shouldProxy)
|
||||
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
||||
else
|
||||
get = new EepGet(_context, false, null, 0, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
||||
get.addStatusListener(this);
|
||||
if (get.fetch()) {
|
||||
_lastModified = get.getLastModified();
|
||||
_invalidated = false;
|
||||
} else {
|
||||
// backup news location - always proxied
|
||||
_tempFile.delete();
|
||||
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL, true, null, _lastModified);
|
||||
get.addStatusListener(this);
|
||||
if (get.fetch())
|
||||
_lastModified = get.getLastModified();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error fetching the news", t);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldFetchUnsigned() {
|
||||
String url = _context.getProperty(ConfigUpdateHandler.PROP_ZIP_URL);
|
||||
return url != null && url.length() > 0 &&
|
||||
_context.getBooleanProperty(ConfigUpdateHandler.PROP_UPDATE_UNSIGNED) &&
|
||||
!dontInstall();
|
||||
}
|
||||
|
||||
/**
|
||||
* HEAD the update url, and if the last-mod time is newer than the last update we
|
||||
* downloaded, as stored in the properties, then we download it using eepget.
|
||||
*/
|
||||
public void fetchUnsignedHead() {
|
||||
String url = _context.getProperty(ConfigUpdateHandler.PROP_ZIP_URL);
|
||||
if (url == null || url.length() <= 0)
|
||||
return;
|
||||
// assume always proxied for now
|
||||
//boolean shouldProxy = Boolean.parseBoolean(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY));
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT_INT);
|
||||
|
||||
try {
|
||||
EepHead get = new EepHead(_context, proxyHost, proxyPort, 0, url);
|
||||
if (get.fetch()) {
|
||||
String lastmod = get.getLastModified();
|
||||
if (lastmod != null) {
|
||||
long modtime = RFC822Date.parse822Date(lastmod);
|
||||
if (modtime <= 0) return;
|
||||
String lastUpdate = _context.getProperty(UpdateHandler.PROP_LAST_UPDATE_TIME);
|
||||
if (lastUpdate == null) {
|
||||
// we don't know what version you have, so stamp it with the current time,
|
||||
// and we'll look for something newer next time around.
|
||||
_context.router().saveConfig(UpdateHandler.PROP_LAST_UPDATE_TIME,
|
||||
Long.toString(_context.clock().now()));
|
||||
return;
|
||||
}
|
||||
long ms = 0;
|
||||
try {
|
||||
ms = Long.parseLong(lastUpdate);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
if (ms <= 0) return;
|
||||
if (modtime > ms) {
|
||||
_unsignedUpdateAvailable = true;
|
||||
// '07-Jul 21:09 UTC' with month name in the system locale
|
||||
_unsignedUpdateVersion = (new SimpleDateFormat("dd-MMM HH:mm")).format(new Date(modtime)) + " UTC";
|
||||
if (shouldInstall())
|
||||
fetchUnsigned();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error fetching the unsigned update", t);
|
||||
}
|
||||
}
|
||||
|
||||
public void fetchUnsigned() {
|
||||
String url = _context.getProperty(ConfigUpdateHandler.PROP_ZIP_URL);
|
||||
if (url == null || url.length() <= 0)
|
||||
return;
|
||||
UpdateHandler handler = new UnsignedUpdateHandler(_context, url,
|
||||
_unsignedUpdateVersion);
|
||||
handler.update();
|
||||
}
|
||||
|
||||
private static final String VERSION_STRING = "version=\"" + RouterVersion.VERSION + "\"";
|
||||
private static final String VERSION_PREFIX = "version=\"";
|
||||
private void checkForUpdates() {
|
||||
_updateAvailable = false;
|
||||
if ( (!_newsFile.exists()) || (_newsFile.length() <= 0) ) return;
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(_newsFile);
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
while (DataHelper.readLine(in, buf)) {
|
||||
int index = buf.indexOf(VERSION_PREFIX);
|
||||
if (index == -1) {
|
||||
// skip
|
||||
} else {
|
||||
int end = buf.indexOf("\"", index + VERSION_PREFIX.length());
|
||||
if (end > index) {
|
||||
String ver = buf.substring(index+VERSION_PREFIX.length(), end);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Found version: [" + ver + "]");
|
||||
if (TrustedUpdate.needsUpdate(RouterVersion.VERSION, ver)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Our version is out of date, update!");
|
||||
_updateVersion = ver;
|
||||
break;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Our version is current");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (buf.indexOf(VERSION_STRING) != -1) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Our version found, no need to update: " + buf.toString());
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("No match in " + buf.toString());
|
||||
}
|
||||
buf.setLength(0);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error checking the news for an update", ioe);
|
||||
return;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
// could not find version="0.5.0.1", so there must be an update ;)
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Our version was NOT found (" + RouterVersion.VERSION + "), update needed");
|
||||
_updateAvailable = !dontInstall();
|
||||
|
||||
if (shouldInstall()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Policy requests update, so we update");
|
||||
UpdateHandler handler = new UpdateHandler(_context);
|
||||
handler.update();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Policy requests manual update, so we do nothing");
|
||||
}
|
||||
}
|
||||
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||
// ignore
|
||||
}
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
// ignore
|
||||
}
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
|
||||
|
||||
long now = _context.clock().now();
|
||||
if (_tempFile.exists()) {
|
||||
boolean copied = FileUtil.copy(_tempFile, _newsFile, true, false);
|
||||
if (copied) {
|
||||
_lastUpdated = now;
|
||||
_tempFile.delete();
|
||||
checkForUpdates();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Failed to copy the news file!");
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Transfer complete, but no file? - probably 304 Not Modified");
|
||||
}
|
||||
_lastFetch = now;
|
||||
_context.router().saveConfig(PROP_LAST_CHECKED, Long.toString(now));
|
||||
}
|
||||
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Failed to fetch the news from " + url);
|
||||
_tempFile.delete();
|
||||
}
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||
public void attempting(String url) {}
|
||||
}
|
@ -2,6 +2,11 @@ package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.update.ConsoleUpdateManager;
|
||||
import static net.i2p.update.UpdateType.*;
|
||||
|
||||
/**
|
||||
* If news file does not exist, use file from the initialNews directory
|
||||
* in $I2P
|
||||
@ -10,6 +15,86 @@ import java.io.File;
|
||||
*/
|
||||
public class NewsHelper extends ContentHelper {
|
||||
|
||||
public static final String PROP_LAST_UPDATE_TIME = "router.updateLastDownloaded";
|
||||
/** @since 0.8.12 */
|
||||
private static final String PROP_LAST_HIDDEN = "routerconsole.newsLastHidden";
|
||||
/** @since 0.9.2 */
|
||||
public static final String PROP_LAST_CHECKED = "routerconsole.newsLastChecked";
|
||||
/** @since 0.9.2 */
|
||||
public static final String PROP_LAST_UPDATED = "routerconsole.newsLastUpdated";
|
||||
public static final String NEWS_FILE = "docs/news.xml";
|
||||
|
||||
/**
|
||||
* If ANY update is in progress.
|
||||
* @since 0.9.2 was stored in system properties
|
||||
*/
|
||||
public static boolean isAnyUpdateInProgress() {
|
||||
ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
|
||||
if (mgr == null) return false;
|
||||
return mgr.isUpdateInProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
* If a signed or unsigned router update is in progress.
|
||||
* Does NOT cover plugins, news, etc.
|
||||
* @since 0.9.2 was stored in system properties
|
||||
*/
|
||||
public static boolean isUpdateInProgress() {
|
||||
ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
|
||||
if (mgr == null) return false;
|
||||
return mgr.isUpdateInProgress(ROUTER_SIGNED) ||
|
||||
mgr.isUpdateInProgress(ROUTER_UNSIGNED) ||
|
||||
mgr.isUpdateInProgress(TYPE_DUMMY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.2 moved from NewsFetcher
|
||||
*/
|
||||
public static boolean isUpdateAvailable() {
|
||||
ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
|
||||
if (mgr == null) return false;
|
||||
return mgr.getUpdateAvailable(ROUTER_SIGNED) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if none
|
||||
* @since 0.9.2 moved from NewsFetcher
|
||||
*/
|
||||
public static String updateVersion() {
|
||||
ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
|
||||
if (mgr == null) return null;
|
||||
return mgr.getUpdateAvailable(ROUTER_SIGNED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.2 moved from NewsFetcher
|
||||
*/
|
||||
public static boolean isUnsignedUpdateAvailable() {
|
||||
ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
|
||||
if (mgr == null) return false;
|
||||
return mgr.getUpdateAvailable(ROUTER_UNSIGNED) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if none
|
||||
* @since 0.9.2 moved from NewsFetcher
|
||||
*/
|
||||
public static String unsignedUpdateVersion() {
|
||||
ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
|
||||
if (mgr == null) return null;
|
||||
return mgr.getUpdateAvailable(ROUTER_UNSIGNED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return "" if none
|
||||
* @since 0.9.2 moved from UpdateHelper
|
||||
*/
|
||||
public static String getUpdateStatus() {
|
||||
ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
|
||||
if (mgr == null) return "";
|
||||
return mgr.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContent() {
|
||||
File news = new File(_page);
|
||||
@ -18,8 +103,131 @@ public class NewsHelper extends ContentHelper {
|
||||
return super.getContent();
|
||||
}
|
||||
|
||||
/** @since 0.8.12 */
|
||||
/**
|
||||
* Is the news newer than the last time it was hidden?
|
||||
* @since 0.8.12
|
||||
*/
|
||||
public boolean shouldShowNews() {
|
||||
return NewsFetcher.getInstance(_context).shouldShowNews();
|
||||
return shouldShowNews(_context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public static boolean shouldShowNews(RouterContext ctx) {
|
||||
long lastUpdated = lastUpdated(ctx);
|
||||
if (lastUpdated <= 0)
|
||||
return true;
|
||||
String h = ctx.getProperty(PROP_LAST_HIDDEN);
|
||||
if (h == null)
|
||||
return true;
|
||||
long last = 0;
|
||||
try {
|
||||
last = Long.parseLong(h);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
return lastUpdated > last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save config with the timestamp of the current news to hide, or 0 to show
|
||||
* @since 0.8.12
|
||||
*/
|
||||
public void showNews(boolean yes) {
|
||||
showNews(_context, yes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save config with the timestamp of the current news to hide, or 0 to show
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public static void showNews(RouterContext ctx, boolean yes) {
|
||||
long lastUpdated = 0;
|
||||
/////// FIME from props, or from last mod time?
|
||||
long stamp = yes ? 0 : lastUpdated;
|
||||
ctx.router().saveConfig(PROP_LAST_HIDDEN, Long.toString(stamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HTML
|
||||
* @since 0.9.2 moved from NewsFetcher
|
||||
*/
|
||||
public String status() {
|
||||
return status(_context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HTML
|
||||
* @since 0.9.2 moved from NewsFetcher
|
||||
*/
|
||||
public static String status(RouterContext ctx) {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
long now = ctx.clock().now();
|
||||
buf.append("<i>");
|
||||
long lastUpdated = lastUpdated(ctx);
|
||||
long lastFetch = lastChecked(ctx);
|
||||
if (lastUpdated > 0) {
|
||||
buf.append(Messages.getString("News last updated {0} ago.",
|
||||
DataHelper.formatDuration2(now - lastUpdated),
|
||||
ctx))
|
||||
.append('\n');
|
||||
}
|
||||
if (lastFetch > lastUpdated) {
|
||||
buf.append(Messages.getString("News last checked {0} ago.",
|
||||
DataHelper.formatDuration2(now - lastFetch),
|
||||
ctx));
|
||||
}
|
||||
buf.append("</i>");
|
||||
String consoleNonce = System.getProperty("router.consoleNonce");
|
||||
if (lastUpdated > 0 && consoleNonce != null) {
|
||||
if (shouldShowNews(ctx)) {
|
||||
buf.append(" <a href=\"/?news=0&consoleNonce=").append(consoleNonce).append("\">")
|
||||
.append(Messages.getString("Hide news", ctx));
|
||||
} else {
|
||||
buf.append(" <a href=\"/?news=1&consoleNonce=").append(consoleNonce).append("\">")
|
||||
.append(Messages.getString("Show news", ctx));
|
||||
}
|
||||
buf.append("</a>");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.2 moved from NewsFetcher
|
||||
*/
|
||||
public static boolean dontInstall(RouterContext ctx) {
|
||||
File test = new File(ctx.getBaseDir(), "history.txt");
|
||||
boolean readonly = ((test.exists() && !test.canWrite()) || (!ctx.getBaseDir().canWrite()));
|
||||
boolean disabled = ctx.getBooleanProperty(ConfigUpdateHandler.PROP_UPDATE_DISABLED);
|
||||
return readonly || disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public static long lastChecked(RouterContext ctx) {
|
||||
String lc = ctx.getProperty(PROP_LAST_CHECKED);
|
||||
if (lc == null) {
|
||||
try {
|
||||
return Long.parseLong(lc);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the news was last downloaded
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public static long lastUpdated(RouterContext ctx) {
|
||||
String lc = ctx.getProperty(PROP_LAST_UPDATED);
|
||||
if (lc == null) {
|
||||
try {
|
||||
return Long.parseLong(lc);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
File newsFile = new File(ctx.getRouterDir(), NEWS_FILE);
|
||||
long rv = newsFile.lastModified();
|
||||
ctx.router().saveConfig(PROP_LAST_UPDATED, Long.toString(rv));
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
import net.i2p.router.startup.ClientAppConfig;
|
||||
import net.i2p.router.startup.LoadClientAppsJob;
|
||||
import net.i2p.router.update.ConsoleUpdateManager;
|
||||
import static net.i2p.update.UpdateType.*;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
@ -45,8 +47,9 @@ import org.mortbay.jetty.handler.ContextHandlerCollection;
|
||||
*/
|
||||
public class PluginStarter implements Runnable {
|
||||
protected RouterContext _context;
|
||||
static final String PREFIX = "plugin.";
|
||||
static final String ENABLED = ".startOnLoad";
|
||||
public static final String PREFIX = "plugin.";
|
||||
public static final String ENABLED = ".startOnLoad";
|
||||
public static final String PLUGIN_DIR = "plugins";
|
||||
private static final String[] STANDARD_WEBAPPS = { "i2psnark", "i2ptunnel", "susidns",
|
||||
"susimail", "addressbook", "routerconsole" };
|
||||
private static final String[] STANDARD_THEMES = { "images", "light", "dark", "classic",
|
||||
@ -66,7 +69,7 @@ public class PluginStarter implements Runnable {
|
||||
|
||||
public void run() {
|
||||
if (_context.getBooleanPropertyDefaultTrue("plugins.autoUpdate") &&
|
||||
(!Boolean.parseBoolean(System.getProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS))) &&
|
||||
(!NewsHelper.isUpdateInProgress()) &&
|
||||
(!RouterVersion.VERSION.equals(_context.getProperty("router.previousVersion"))))
|
||||
updateAll(_context, true);
|
||||
startPlugins(_context);
|
||||
@ -112,18 +115,24 @@ public class PluginStarter implements Runnable {
|
||||
}
|
||||
if (toUpdate.isEmpty())
|
||||
return;
|
||||
PluginUpdateChecker puc = PluginUpdateChecker.getInstance(ctx);
|
||||
if (puc.isRunning())
|
||||
|
||||
ConsoleUpdateManager mgr = (ConsoleUpdateManager) ctx.updateManager();
|
||||
if (mgr == null)
|
||||
return;
|
||||
if (mgr.isUpdateInProgress())
|
||||
return;
|
||||
|
||||
if (delay) {
|
||||
// wait for proxy
|
||||
System.setProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS, "true");
|
||||
puc.setAppStatus(Messages.getString("Checking for plugin updates", ctx));
|
||||
try {
|
||||
Thread.sleep(3*60*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
System.setProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS, "false");
|
||||
mgr.update(TYPE_DUMMY, 3*60*1000);
|
||||
mgr.notifyProgress(null, Messages.getString("Checking for plugin updates", ctx));
|
||||
int loop = 0;
|
||||
do {
|
||||
try {
|
||||
Thread.sleep(5*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
if (loop++ > 40) break;
|
||||
} while (mgr.isUpdateInProgress(TYPE_DUMMY));
|
||||
}
|
||||
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
@ -132,34 +141,32 @@ public class PluginStarter implements Runnable {
|
||||
String appName = entry.getKey();
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Checking for update plugin: " + appName);
|
||||
puc.update(appName);
|
||||
do {
|
||||
try {
|
||||
Thread.sleep(5*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
} while (puc.isRunning());
|
||||
if (!puc.isNewerAvailable()) {
|
||||
|
||||
// blocking
|
||||
if (mgr.checkAvailable(PLUGIN, appName, 60*1000) == null) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("No update available for plugin: " + appName);
|
||||
continue;
|
||||
}
|
||||
PluginUpdateHandler puh = PluginUpdateHandler.getInstance(ctx);
|
||||
String url = entry.getValue();
|
||||
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Updating plugin: " + appName);
|
||||
puh.update(url);
|
||||
mgr.update(PLUGIN, appName, 30*60*1000);
|
||||
int loop = 0;
|
||||
do {
|
||||
try {
|
||||
Thread.sleep(5*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
} while (puh.isRunning());
|
||||
if (puh.wasUpdateSuccessful())
|
||||
if (loop++ > 40) break;
|
||||
} while (mgr.isUpdateInProgress(PLUGIN, appName));
|
||||
|
||||
if (mgr.getUpdateAvailable(PLUGIN, appName) != null)
|
||||
updated++;
|
||||
}
|
||||
if (updated > 0)
|
||||
puc.setDoneStatus(ngettext("1 plugin updated", "{0} plugins updated", updated, ctx));
|
||||
mgr.notifyComplete(null, ngettext("1 plugin updated", "{0} plugins updated", updated, ctx));
|
||||
else
|
||||
puc.setDoneStatus(Messages.getString("Plugin update check complete", ctx));
|
||||
mgr.notifyComplete(null, Messages.getString("Plugin update check complete", ctx));
|
||||
}
|
||||
|
||||
/** this shouldn't throw anything */
|
||||
@ -189,9 +196,9 @@ public class PluginStarter implements Runnable {
|
||||
* @return true on success
|
||||
* @throws just about anything, caller would be wise to catch Throwable
|
||||
*/
|
||||
static boolean startPlugin(RouterContext ctx, String appName) throws Exception {
|
||||
public static boolean startPlugin(RouterContext ctx, String appName) throws Exception {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PLUGIN_DIR + '/' + appName);
|
||||
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
||||
log.error("Cannot start nonexistent plugin: " + appName);
|
||||
disablePlugin(appName);
|
||||
@ -199,7 +206,7 @@ public class PluginStarter implements Runnable {
|
||||
}
|
||||
|
||||
// Do we need to extract an update?
|
||||
File pluginUpdate = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName + "/app.xpi2p.zip" );
|
||||
File pluginUpdate = new File(ctx.getConfigDir(), PLUGIN_DIR + '/' + appName + "/app.xpi2p.zip" );
|
||||
if(pluginUpdate.exists()) {
|
||||
// Compare the start time of the router with the plugin.
|
||||
if(ctx.router().getWhenStarted() > pluginUpdate.lastModified()) {
|
||||
@ -363,9 +370,9 @@ public class PluginStarter implements Runnable {
|
||||
* @return true on success
|
||||
* @throws just about anything, caller would be wise to catch Throwable
|
||||
*/
|
||||
static boolean stopPlugin(RouterContext ctx, String appName) throws Exception {
|
||||
public static boolean stopPlugin(RouterContext ctx, String appName) throws Exception {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PLUGIN_DIR + '/' + appName);
|
||||
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
||||
log.error("Cannot stop nonexistent plugin: " + appName);
|
||||
return false;
|
||||
@ -424,7 +431,7 @@ public class PluginStarter implements Runnable {
|
||||
/** @return true on success - caller should call stopPlugin() first */
|
||||
static boolean deletePlugin(RouterContext ctx, String appName) throws Exception {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PLUGIN_DIR + '/' + appName);
|
||||
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
||||
log.error("Cannot delete nonexistent plugin: " + appName);
|
||||
return false;
|
||||
@ -469,7 +476,7 @@ public class PluginStarter implements Runnable {
|
||||
|
||||
/** plugin.config */
|
||||
public static Properties pluginProperties(I2PAppContext ctx, String appName) {
|
||||
File cfgFile = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName + '/' + "plugin.config");
|
||||
File cfgFile = new File(ctx.getConfigDir(), PLUGIN_DIR + '/' + appName + '/' + "plugin.config");
|
||||
Properties rv = new Properties();
|
||||
try {
|
||||
DataHelper.loadProps(rv, cfgFile);
|
||||
@ -530,7 +537,7 @@ public class PluginStarter implements Runnable {
|
||||
*/
|
||||
public static List<String> getPlugins() {
|
||||
List<String> rv = new ArrayList();
|
||||
File pluginDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), PluginUpdateHandler.PLUGIN_DIR);
|
||||
File pluginDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), PLUGIN_DIR);
|
||||
File[] files = pluginDir.listFiles();
|
||||
if (files == null)
|
||||
return rv;
|
||||
|
@ -1,200 +0,0 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.PartialEepGet;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @since 0.7.12
|
||||
* @author zzz
|
||||
*/
|
||||
public class PluginUpdateChecker extends UpdateHandler {
|
||||
private static PluginUpdateCheckerRunner _pluginUpdateCheckerRunner;
|
||||
private String _appName;
|
||||
private String _oldVersion;
|
||||
private String _xpi2pURL;
|
||||
private volatile boolean _isNewerAvailable;
|
||||
|
||||
private static PluginUpdateChecker _instance;
|
||||
public static final synchronized PluginUpdateChecker getInstance(RouterContext ctx) {
|
||||
if (_instance != null)
|
||||
return _instance;
|
||||
_instance = new PluginUpdateChecker(ctx);
|
||||
return _instance;
|
||||
}
|
||||
|
||||
private PluginUpdateChecker(RouterContext ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* check all plugins
|
||||
* @deprecated not finished
|
||||
*/
|
||||
public void update() {
|
||||
Thread t = new I2PAppThread(new AllCheckerRunner(), "AllAppChecker", true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* check all plugins
|
||||
* @deprecated not finished
|
||||
*/
|
||||
public class AllCheckerRunner implements Runnable {
|
||||
public void run() {
|
||||
List<String> plugins = PluginStarter.getPlugins();
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
/** check a single plugin */
|
||||
public void update(String appName) {
|
||||
// don't block waiting for the other one to finish
|
||||
if ("true".equals(System.getProperty(PROP_UPDATE_IN_PROGRESS))) {
|
||||
_log.error("Update already running");
|
||||
return;
|
||||
}
|
||||
synchronized (UpdateHandler.class) {
|
||||
Properties props = PluginStarter.pluginProperties(_context, appName);
|
||||
String oldVersion = props.getProperty("version");
|
||||
String xpi2pURL = props.getProperty("updateURL");
|
||||
if (oldVersion == null || xpi2pURL == null) {
|
||||
updateStatus("<b>" + _("Cannot check, plugin {0} is not installed", appName) + "</b>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pluginUpdateCheckerRunner == null)
|
||||
_pluginUpdateCheckerRunner = new PluginUpdateCheckerRunner();
|
||||
if (_pluginUpdateCheckerRunner.isRunning())
|
||||
return;
|
||||
_xpi2pURL = xpi2pURL;
|
||||
_appName = appName;
|
||||
_oldVersion = oldVersion;
|
||||
_isNewerAvailable = false;
|
||||
System.setProperty(PROP_UPDATE_IN_PROGRESS, "true");
|
||||
I2PAppThread update = new I2PAppThread(_pluginUpdateCheckerRunner, "AppChecker", true);
|
||||
update.start();
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.8.13 */
|
||||
public void setAppStatus(String status) {
|
||||
updateStatus(status);
|
||||
}
|
||||
|
||||
/** @since 0.8.13 */
|
||||
public void setDoneStatus(String status) {
|
||||
updateStatus(status);
|
||||
scheduleStatusClean(status);
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return _pluginUpdateCheckerRunner != null && _pluginUpdateCheckerRunner.isRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
// FIXME
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @since 0.8.13 */
|
||||
public boolean isNewerAvailable() {
|
||||
return _isNewerAvailable;
|
||||
}
|
||||
|
||||
private void scheduleStatusClean(String msg) {
|
||||
_context.simpleScheduler().addEvent(new Cleaner(msg), 20*60*1000);
|
||||
}
|
||||
|
||||
private class Cleaner implements SimpleTimer.TimedEvent {
|
||||
private final String _msg;
|
||||
public Cleaner(String msg) {
|
||||
_msg = msg;
|
||||
}
|
||||
public void timeReached() {
|
||||
if (_msg.equals(getStatus()))
|
||||
updateStatus("");
|
||||
}
|
||||
}
|
||||
|
||||
public class PluginUpdateCheckerRunner extends UpdateRunner implements Runnable, EepGet.StatusListener {
|
||||
ByteArrayOutputStream _baos;
|
||||
|
||||
public PluginUpdateCheckerRunner() {
|
||||
super();
|
||||
_baos = new ByteArrayOutputStream(TrustedUpdate.HEADER_BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void update() {
|
||||
_isNewerAvailable = false;
|
||||
updateStatus("<b>" + _("Checking for update of plugin {0}", _appName) + "</b>");
|
||||
// use the same settings as for updater
|
||||
// always proxy, or else FIXME
|
||||
//boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
|
||||
_baos.reset();
|
||||
try {
|
||||
_get = new PartialEepGet(_context, proxyHost, proxyPort, _baos, _xpi2pURL, TrustedUpdate.HEADER_BYTES);
|
||||
_get.addStatusListener(PluginUpdateCheckerRunner.this);
|
||||
_get.fetch(CONNECT_TIMEOUT);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error checking update for plugin", t);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNewerAvailable() {
|
||||
return _isNewerAvailable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
String newVersion = TrustedUpdate.getVersionString(new ByteArrayInputStream(_baos.toByteArray()));
|
||||
boolean newer = (new VersionComparator()).compare(newVersion, _oldVersion) > 0;
|
||||
String msg;
|
||||
if (newer) {
|
||||
msg = "<b>" + _("New plugin version {0} is available", newVersion) + "</b>";
|
||||
_isNewerAvailable = true;
|
||||
} else {
|
||||
msg = "<b>" + _("No new version is available for plugin {0}", _appName) + "</b>";
|
||||
}
|
||||
updateStatus(msg);
|
||||
scheduleStatusClean(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
File f = new File(_updateFile);
|
||||
f.delete();
|
||||
String msg = "<b>" + _("Update check failed for plugin {0}", _appName) + "</b>";
|
||||
updateStatus(msg);
|
||||
scheduleStatusClean(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import net.i2p.apps.systray.SysTray;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.update.ConsoleUpdateManager;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
@ -568,10 +569,8 @@ public class RouterConsoleRunner {
|
||||
if (contexts != null) {
|
||||
RouterContext ctx = contexts.get(0);
|
||||
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(ctx);
|
||||
Thread newsThread = new I2PAppThread(fetcher, "NewsFetcher", true);
|
||||
newsThread.setPriority(Thread.NORM_PRIORITY - 1);
|
||||
newsThread.start();
|
||||
ConsoleUpdateManager um = new ConsoleUpdateManager(ctx);
|
||||
um.start();
|
||||
|
||||
if (PluginStarter.pluginsEnabled(ctx)) {
|
||||
t = new I2PAppThread(new PluginStarter(ctx), "PluginStarter", true);
|
||||
@ -579,7 +578,6 @@ public class RouterConsoleRunner {
|
||||
t.start();
|
||||
ctx.addShutdownTask(new PluginStopper(ctx));
|
||||
}
|
||||
ctx.addShutdownTask(new NewsShutdown(fetcher, newsThread));
|
||||
// stat summarizer registers its own hook
|
||||
ctx.addShutdownTask(new ServerShutdown());
|
||||
ConfigServiceHandler.registerSignalHandler(ctx);
|
||||
@ -751,22 +749,6 @@ public class RouterConsoleRunner {
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.8.8 */
|
||||
private static class NewsShutdown implements Runnable {
|
||||
private final NewsFetcher _fetcher;
|
||||
private final Thread _newsThread;
|
||||
|
||||
public NewsShutdown(NewsFetcher fetcher, Thread t) {
|
||||
_fetcher = fetcher;
|
||||
_newsThread = t;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_fetcher.shutdown();
|
||||
_newsThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public static Properties webAppProperties() {
|
||||
return webAppProperties(I2PAppContext.getGlobalContext().getConfigDir().getAbsolutePath());
|
||||
}
|
||||
|
@ -628,19 +628,19 @@ public class SummaryHelper extends HelperBase {
|
||||
********/
|
||||
|
||||
public boolean updateAvailable() {
|
||||
return NewsFetcher.getInstance(_context).updateAvailable();
|
||||
return NewsHelper.isUpdateAvailable();
|
||||
}
|
||||
|
||||
public boolean unsignedUpdateAvailable() {
|
||||
return NewsFetcher.getInstance(_context).unsignedUpdateAvailable();
|
||||
return NewsHelper.isUnsignedUpdateAvailable();
|
||||
}
|
||||
|
||||
public String getUpdateVersion() {
|
||||
return NewsFetcher.getInstance(_context).updateVersion();
|
||||
return NewsHelper.updateVersion();
|
||||
}
|
||||
|
||||
public String getUnsignedUpdateVersion() {
|
||||
return NewsFetcher.getInstance(_context).unsignedUpdateVersion();
|
||||
return NewsHelper.unsignedUpdateVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -650,12 +650,12 @@ public class SummaryHelper extends HelperBase {
|
||||
public String getUpdateStatus() {
|
||||
StringBuilder buf = new StringBuilder(512);
|
||||
// display all the time so we display the final failure message, and plugin update messages too
|
||||
String status = UpdateHandler.getStatus();
|
||||
String status = NewsHelper.getUpdateStatus();
|
||||
if (status.length() > 0) {
|
||||
buf.append("<h4>").append(status).append("</h4>\n");
|
||||
}
|
||||
if (updateAvailable() || unsignedUpdateAvailable()) {
|
||||
if ("true".equals(System.getProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS))) {
|
||||
if (NewsHelper.isUpdateInProgress()) {
|
||||
// nothing
|
||||
} else if(
|
||||
// isDone() is always false for now, see UpdateHandler
|
||||
|
@ -1,130 +0,0 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.util.RFC822Date;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Handles the request to update the router by firing off an
|
||||
* {@link net.i2p.util.EepGet} call to download the latest unsigned zip file
|
||||
* and displaying the status to anyone who asks.
|
||||
* </p>
|
||||
* <p>After the download completes the signed update file is copied to the
|
||||
* router directory, and if configured the router is restarted to complete
|
||||
* the update process.
|
||||
* </p>
|
||||
*/
|
||||
public class UnsignedUpdateHandler extends UpdateHandler {
|
||||
private static UnsignedUpdateRunner _unsignedUpdateRunner;
|
||||
private String _zipURL;
|
||||
private String _zipVersion;
|
||||
|
||||
public UnsignedUpdateHandler(RouterContext ctx, String zipURL, String version) {
|
||||
super(ctx);
|
||||
_zipURL = zipURL;
|
||||
_zipVersion = version;
|
||||
_updateFile = (new File(ctx.getTempDir(), "tmp" + ctx.random().nextInt() + Router.UPDATE_FILE)).getAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
// don't block waiting for the other one to finish
|
||||
if ("true".equals(System.getProperty(PROP_UPDATE_IN_PROGRESS))) {
|
||||
_log.error("Update already running");
|
||||
return;
|
||||
}
|
||||
synchronized (UpdateHandler.class) {
|
||||
if (_unsignedUpdateRunner == null) {
|
||||
_unsignedUpdateRunner = new UnsignedUpdateRunner();
|
||||
}
|
||||
if (_unsignedUpdateRunner.isRunning()) {
|
||||
return;
|
||||
} else {
|
||||
System.setProperty(PROP_UPDATE_IN_PROGRESS, "true");
|
||||
I2PAppThread update = new I2PAppThread(_unsignedUpdateRunner, "UnsignedUpdate");
|
||||
update.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Eepget the .zip file to the temp dir, then copy it over
|
||||
*/
|
||||
public class UnsignedUpdateRunner extends UpdateRunner implements Runnable, EepGet.StatusListener {
|
||||
public UnsignedUpdateRunner() {
|
||||
super();
|
||||
}
|
||||
|
||||
/** Get the file */
|
||||
@Override
|
||||
protected void update() {
|
||||
updateStatus("<b>" + _("Updating") + "</b>");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Starting unsigned update URL: " + _zipURL);
|
||||
// always proxy for now
|
||||
//boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
|
||||
try {
|
||||
// 40 retries!!
|
||||
_get = new EepGet(_context, proxyHost, proxyPort, 40, _updateFile, _zipURL, false);
|
||||
_get.addStatusListener(UnsignedUpdateRunner.this);
|
||||
_get.fetch(CONNECT_TIMEOUT, -1, INACTIVITY_TIMEOUT);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error updating", t);
|
||||
}
|
||||
}
|
||||
|
||||
/** eepget listener callback Overrides */
|
||||
@Override
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
File updFile = new File(_updateFile);
|
||||
if (FileUtil.verifyZip(updFile)) {
|
||||
updateStatus("<b>" + _("Update downloaded") + "</b>");
|
||||
} else {
|
||||
updFile.delete();
|
||||
updateStatus("<b>" + _("Unsigned update file from {0} is corrupt", url) + "</b>");
|
||||
_log.log(Log.CRIT, "Corrupt zip file from " + url);
|
||||
return;
|
||||
}
|
||||
File to = new File(_context.getRouterDir(), Router.UPDATE_FILE);
|
||||
boolean copied = FileUtil.copy(updFile, to, true, false);
|
||||
if (copied) {
|
||||
updFile.delete();
|
||||
String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
|
||||
this.done = true;
|
||||
String lastmod = _get.getLastModified();
|
||||
long modtime = 0;
|
||||
if (lastmod != null)
|
||||
modtime = RFC822Date.parse822Date(lastmod);
|
||||
if (modtime <= 0)
|
||||
modtime = _context.clock().now();
|
||||
_context.router().saveConfig(PROP_LAST_UPDATE_TIME, "" + modtime);
|
||||
if ("install".equals(policy)) {
|
||||
_log.log(Log.CRIT, "Update was downloaded, restarting to install it");
|
||||
updateStatus("<b>" + _("Update downloaded") + "</b><br>" + _("Restarting"));
|
||||
restart();
|
||||
} else {
|
||||
_log.log(Log.CRIT, "Update was downloaded, will be installed at next restart");
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
buf.append("<b>").append(_("Update downloaded")).append("</b><br>");
|
||||
if (_context.hasWrapper())
|
||||
buf.append(_("Click Restart to install"));
|
||||
else
|
||||
buf.append(_("Click Shutdown and restart to install"));
|
||||
buf.append(' ').append(_("Version {0}", _zipVersion));
|
||||
updateStatus(buf.toString());
|
||||
}
|
||||
} else {
|
||||
_log.log(Log.CRIT, "Failed copy to " + to);
|
||||
updateStatus("<b>" + _("Failed copy to {0}", to.getAbsolutePath()) + "</b>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +1,10 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
import net.i2p.router.util.RFC822Date;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.router.update.ConsoleUpdateManager;
|
||||
import net.i2p.update.UpdateType;
|
||||
import static net.i2p.update.UpdateType.*;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PartialEepGet;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
/**
|
||||
* <p>Handles the request to update the router by firing one or more
|
||||
@ -31,31 +16,22 @@ import net.i2p.util.VersionComparator;
|
||||
* of the signed update file is unpacked and the router is restarted to complete
|
||||
* the update process.
|
||||
* </p>
|
||||
*
|
||||
* This is like a FormHandler but we don't extend it, as we don't have the message area, etc.
|
||||
*/
|
||||
public class UpdateHandler {
|
||||
protected static UpdateRunner _updateRunner;
|
||||
protected RouterContext _context;
|
||||
protected Log _log;
|
||||
protected String _updateFile;
|
||||
private static String _status = "";
|
||||
private String _action;
|
||||
private String _nonce;
|
||||
|
||||
protected static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
|
||||
static final String PROP_UPDATE_IN_PROGRESS = "net.i2p.router.web.UpdateHandler.updateInProgress";
|
||||
protected static final String PROP_LAST_UPDATE_TIME = "router.updateLastDownloaded";
|
||||
|
||||
protected static final long CONNECT_TIMEOUT = 55*1000;
|
||||
protected static final long INACTIVITY_TIMEOUT = 5*60*1000;
|
||||
protected static final long NOPROXY_INACTIVITY_TIMEOUT = 60*1000;
|
||||
|
||||
public UpdateHandler() {
|
||||
this(ContextHelper.getContext(null));
|
||||
}
|
||||
|
||||
public UpdateHandler(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(UpdateHandler.class);
|
||||
_updateFile = (new File(ctx.getRouterDir(), SIGNED_UPDATE_FILE)).getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,274 +65,21 @@ public class UpdateHandler {
|
||||
if (_nonce.equals(System.getProperty("net.i2p.router.web.UpdateHandler.nonce")) ||
|
||||
_nonce.equals(System.getProperty("net.i2p.router.web.UpdateHandler.noncePrev"))) {
|
||||
if (_action.contains("Unsigned")) {
|
||||
// Not us, have NewsFetcher instantiate the correct class.
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(_context);
|
||||
fetcher.fetchUnsigned();
|
||||
update(ROUTER_UNSIGNED);
|
||||
} else {
|
||||
update();
|
||||
update(ROUTER_SIGNED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void update() {
|
||||
// don't block waiting for the other one to finish
|
||||
if ("true".equals(System.getProperty(PROP_UPDATE_IN_PROGRESS))) {
|
||||
private void update(UpdateType type) {
|
||||
ConsoleUpdateManager mgr = (ConsoleUpdateManager) _context.updateManager();
|
||||
if (mgr == null)
|
||||
return;
|
||||
if (mgr.isUpdateInProgress(ROUTER_SIGNED) || mgr.isUpdateInProgress(ROUTER_UNSIGNED)) {
|
||||
_log.error("Update already running");
|
||||
return;
|
||||
}
|
||||
synchronized (UpdateHandler.class) {
|
||||
if (_updateRunner == null)
|
||||
_updateRunner = new UpdateRunner();
|
||||
if (_updateRunner.isRunning()) {
|
||||
return;
|
||||
} else {
|
||||
System.setProperty(PROP_UPDATE_IN_PROGRESS, "true");
|
||||
I2PAppThread update = new I2PAppThread(_updateRunner, "SignedUpdate");
|
||||
update.start();
|
||||
}
|
||||
}
|
||||
mgr.update(type);
|
||||
}
|
||||
|
||||
public static String getStatus() {
|
||||
return _status;
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return false;
|
||||
// this needs to be fixed and tested
|
||||
//if(this._updateRunner == null)
|
||||
// return true;
|
||||
//return this._updateRunner.isDone();
|
||||
}
|
||||
|
||||
public class UpdateRunner implements Runnable, EepGet.StatusListener {
|
||||
protected volatile boolean _isRunning;
|
||||
protected boolean done;
|
||||
protected EepGet _get;
|
||||
protected final DecimalFormat _pct = new DecimalFormat("0.0%");
|
||||
/** tells the listeners what mode we are in */
|
||||
private boolean _isPartial;
|
||||
/** set by the listeners on completion */
|
||||
private boolean _isNewer;
|
||||
private ByteArrayOutputStream _baos;
|
||||
|
||||
public UpdateRunner() {
|
||||
_isRunning = false;
|
||||
this.done = false;
|
||||
updateStatus("<b>" + _("Updating") + "</b>");
|
||||
}
|
||||
public boolean isRunning() { return _isRunning; }
|
||||
public boolean isDone() {
|
||||
return this.done;
|
||||
}
|
||||
public void run() {
|
||||
_isRunning = true;
|
||||
update();
|
||||
System.setProperty(PROP_UPDATE_IN_PROGRESS, "false");
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected void update() {
|
||||
// Do a PartialEepGet on the selected URL, check for version we expect,
|
||||
// and loop if it isn't what we want.
|
||||
// This will allows us to do a release without waiting for the last host to install the update.
|
||||
// Alternative: In bytesTransferred(), Check the data in the output file after
|
||||
// we've received at least 56 bytes. Need a cancel() method in EepGet ?
|
||||
|
||||
boolean shouldProxy = Boolean.parseBoolean(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY));
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
|
||||
|
||||
List<String> urls = getUpdateURLs();
|
||||
if (urls.isEmpty()) {
|
||||
// not likely, don't bother translating
|
||||
updateStatus("<b>Update source list is empty, cannot download update</b>");
|
||||
_log.log(Log.CRIT, "Update source list is empty - cannot download update");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldProxy)
|
||||
_baos = new ByteArrayOutputStream(TrustedUpdate.HEADER_BYTES);
|
||||
for (String updateURL : urls) {
|
||||
updateStatus("<b>" + _("Updating from {0}", linkify(updateURL)) + "</b>");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Selected update URL: " + updateURL);
|
||||
|
||||
// Check the first 56 bytes for the version
|
||||
if (shouldProxy) {
|
||||
_isPartial = true;
|
||||
_isNewer = false;
|
||||
_baos.reset();
|
||||
try {
|
||||
// no retries
|
||||
_get = new PartialEepGet(_context, proxyHost, proxyPort, _baos, updateURL, TrustedUpdate.HEADER_BYTES);
|
||||
_get.addStatusListener(UpdateRunner.this);
|
||||
_get.fetch(CONNECT_TIMEOUT);
|
||||
} catch (Throwable t) {
|
||||
_isNewer = false;
|
||||
}
|
||||
_isPartial = false;
|
||||
if (!_isNewer)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now get the whole thing
|
||||
try {
|
||||
if (shouldProxy)
|
||||
// 40 retries!!
|
||||
_get = new EepGet(_context, proxyHost, proxyPort, 40, _updateFile, updateURL, false);
|
||||
else
|
||||
_get = new EepGet(_context, 1, _updateFile, updateURL, false);
|
||||
_get.addStatusListener(UpdateRunner.this);
|
||||
_get.fetch(CONNECT_TIMEOUT, -1, shouldProxy ? INACTIVITY_TIMEOUT : NOPROXY_INACTIVITY_TIMEOUT);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error updating", t);
|
||||
}
|
||||
if (this.done)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// EepGet Listeners below.
|
||||
// We use the same for both the partial and the full EepGet,
|
||||
// with a couple of adjustments depending on which mode.
|
||||
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||
_isNewer = false;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Attempt failed on " + url, cause);
|
||||
// ignored
|
||||
}
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
if (_isPartial)
|
||||
return;
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
double pct = ((double)alreadyTransferred + (double)currentWrite) /
|
||||
((double)alreadyTransferred + (double)currentWrite + bytesRemaining);
|
||||
synchronized (_pct) {
|
||||
buf.append(_("{0} downloaded", _pct.format(pct)));
|
||||
}
|
||||
buf.append("<br>\n");
|
||||
buf.append(DataHelper.formatSize2(currentWrite + alreadyTransferred))
|
||||
.append("B / ")
|
||||
.append(DataHelper.formatSize2(currentWrite + alreadyTransferred + bytesRemaining))
|
||||
.append("B");
|
||||
updateStatus(buf.toString());
|
||||
}
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
if (_isPartial) {
|
||||
// Compare version with what we have now
|
||||
String newVersion = TrustedUpdate.getVersionString(new ByteArrayInputStream(_baos.toByteArray()));
|
||||
boolean newer = (new VersionComparator()).compare(newVersion, RouterVersion.VERSION) > 0;
|
||||
if (!newer) {
|
||||
updateStatus("<b>" + _("No new version found at {0}", linkify(url)) + "</b>");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Found old version \"" + newVersion + "\" at " + url);
|
||||
}
|
||||
_isNewer = newer;
|
||||
return;
|
||||
}
|
||||
// Process the .sud/.su2 file
|
||||
updateStatus("<b>" + _("Update downloaded") + "</b>");
|
||||
TrustedUpdate up = new TrustedUpdate(_context);
|
||||
File f = new File(_updateFile);
|
||||
File to = new File(_context.getRouterDir(), Router.UPDATE_FILE);
|
||||
String err = up.migrateVerified(RouterVersion.VERSION, f, to);
|
||||
f.delete();
|
||||
if (err == null) {
|
||||
String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
|
||||
this.done = true;
|
||||
// So unsigned update handler doesn't overwrite unless newer.
|
||||
String lastmod = _get.getLastModified();
|
||||
long modtime = 0;
|
||||
if (lastmod != null)
|
||||
modtime = RFC822Date.parse822Date(lastmod);
|
||||
if (modtime <= 0)
|
||||
modtime = _context.clock().now();
|
||||
_context.router().saveConfig(PROP_LAST_UPDATE_TIME, "" + modtime);
|
||||
if ("install".equals(policy)) {
|
||||
_log.log(Log.CRIT, "Update was VERIFIED, restarting to install it");
|
||||
updateStatus("<b>" + _("Update verified") + "</b><br>" + _("Restarting"));
|
||||
restart();
|
||||
} else {
|
||||
_log.log(Log.CRIT, "Update was VERIFIED, will be installed at next restart");
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
buf.append("<b>").append(_("Update downloaded")).append("<br>");
|
||||
if (_context.hasWrapper())
|
||||
buf.append(_("Click Restart to install"));
|
||||
else
|
||||
buf.append(_("Click Shutdown and restart to install"));
|
||||
if (up.newVersion() != null)
|
||||
buf.append(' ').append(_("Version {0}", up.newVersion()));
|
||||
buf.append("</b>");
|
||||
updateStatus(buf.toString());
|
||||
}
|
||||
} else {
|
||||
_log.log(Log.CRIT, err + " from " + url);
|
||||
updateStatus("<b>" + err + ' ' + _("from {0}", linkify(url)) + " </b>");
|
||||
}
|
||||
}
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
_isNewer = false;
|
||||
// don't display bytesTransferred as it is meaningless
|
||||
_log.error("Update from " + url + " did not download completely (" +
|
||||
bytesRemaining + " remaining after " + currentAttempt + " tries)");
|
||||
|
||||
updateStatus("<b>" + _("Transfer failed from {0}", linkify(url)) + "</b>");
|
||||
}
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||
public void attempting(String url) {}
|
||||
}
|
||||
|
||||
protected void restart() {
|
||||
if (_context.hasWrapper())
|
||||
ConfigServiceHandler.registerWrapperNotifier(_context, Router.EXIT_GRACEFUL_RESTART, false);
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
}
|
||||
|
||||
private List<String> getUpdateURLs() {
|
||||
String URLs = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL, ConfigUpdateHandler.DEFAULT_UPDATE_URL);
|
||||
StringTokenizer tok = new StringTokenizer(URLs, " ,\r\n");
|
||||
List<String> URLList = new ArrayList();
|
||||
while (tok.hasMoreTokens())
|
||||
URLList.add(tok.nextToken().trim());
|
||||
Collections.shuffle(URLList, _context.random());
|
||||
return URLList;
|
||||
}
|
||||
|
||||
protected void updateStatus(String s) {
|
||||
_status = s;
|
||||
}
|
||||
|
||||
protected static String linkify(String url) {
|
||||
return "<a target=\"_blank\" href=\"" + url + "\"/>" + url + "</a>";
|
||||
}
|
||||
|
||||
/** translate a string */
|
||||
protected String _(String s) {
|
||||
return Messages.getString(s, _context);
|
||||
}
|
||||
|
||||
/**
|
||||
* translate a string with a parameter
|
||||
* This is a lot more expensive than _(s), so use sparingly.
|
||||
*
|
||||
* @param s string to be translated containing {0}
|
||||
* The {0} will be replaced by the parameter.
|
||||
* Single quotes must be doubled, i.e. ' -> '' in the string.
|
||||
* @param o parameter, not translated.
|
||||
* To tranlslate parameter also, use _("foo {0} bar", _("baz"))
|
||||
* Do not double the single quotes in the parameter.
|
||||
* Use autoboxing to call with ints, longs, floats, etc.
|
||||
*/
|
||||
protected String _(String s, Object o) {
|
||||
return Messages.getString(s, o, _context);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ public class WebAppConfiguration implements Configuration {
|
||||
File libDir = new File(i2pContext.getBaseDir(), "lib");
|
||||
// FIXME this only works if war is the same name as the plugin
|
||||
File pluginDir = new File(i2pContext.getConfigDir(),
|
||||
PluginUpdateHandler.PLUGIN_DIR + ctxPath);
|
||||
PluginStarter.PLUGIN_DIR + ctxPath);
|
||||
|
||||
File dir = libDir;
|
||||
String cp;
|
||||
|
@ -81,14 +81,15 @@
|
||||
</target>
|
||||
|
||||
<!-- unit tests -->
|
||||
<target name="scalatest.compileTest" depends="jar, scala.init">
|
||||
<target name="scalatest.compileTest" depends="compile, scala.init">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj_scala" />
|
||||
<scalac srcdir="./test/scalatest" destdir="./build/obj_scala" deprecation="on" >
|
||||
<classpath>
|
||||
<pathelement location="${classpath}" />
|
||||
<pathelement location="${scala-library.jar}" />
|
||||
<pathelement location="${scalatest.jar}" />
|
||||
<pathelement location="./build/i2p.jar" />
|
||||
<pathelement location="./build/obj" />
|
||||
</classpath>
|
||||
</scalac>
|
||||
</target>
|
||||
@ -102,6 +103,23 @@
|
||||
<compilerarg line="${javac.compilerargs}" />
|
||||
</javac>
|
||||
</target>
|
||||
<!-- jars with tests -->
|
||||
<target name="jarScalaTest" depends="scalatest.compileTest">
|
||||
<mkdir dir="./build/obj_scala_jar" />
|
||||
<copy todir="./build/obj_scala_jar">
|
||||
<fileset dir="./build/">
|
||||
<include name="obj/**/*.class"/>
|
||||
</fileset>
|
||||
<mapper type="glob" from="obj/*" to="*" />
|
||||
</copy>
|
||||
<copy todir="./build/obj_scala_jar">
|
||||
<fileset dir="./build/">
|
||||
<include name="obj_scala/**/*.class"/>
|
||||
</fileset>
|
||||
<mapper type="glob" from="obj_scala/*" to="*" />
|
||||
</copy>
|
||||
<jar destfile="./build/i2pscalatest.jar" basedir="./build/obj_scala_jar" includes="**/*.class" />
|
||||
</target>
|
||||
<target name="jarTest" depends="junit.compileTest">
|
||||
<jar destfile="./build/i2ptest.jar" basedir="./build/obj" includes="**/*.class" />
|
||||
</target>
|
||||
|
@ -26,6 +26,7 @@ import net.i2p.data.Base64;
|
||||
import net.i2p.data.RoutingKeyGenerator;
|
||||
import net.i2p.internal.InternalClientManager;
|
||||
import net.i2p.stat.StatManager;
|
||||
import net.i2p.update.UpdateManager;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.FileUtil;
|
||||
@ -991,4 +992,13 @@ public class I2PAppContext {
|
||||
_simpleTimer2Initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The controller of router, plugin, and other updates.
|
||||
* @return always null in I2PAppContext, the update manager if in RouterContext and it is registered
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public UpdateManager updateManager() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
79
core/java/src/net/i2p/update/UpdateManager.java
Normal file
79
core/java/src/net/i2p/update/UpdateManager.java
Normal file
@ -0,0 +1,79 @@
|
||||
package net.i2p.update;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.List;;
|
||||
|
||||
/**
|
||||
* The central resource coordinating updates.
|
||||
* This must be registered with the context.
|
||||
*
|
||||
* The UpdateManager starts and stops all updates,
|
||||
* and controls notification to the user.
|
||||
*
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public interface UpdateManager {
|
||||
|
||||
/**
|
||||
* Call multiple times for each type/method pair.
|
||||
* The UpdateManager will then call start()
|
||||
*/
|
||||
public void register(Updater updater, UpdateType type, UpdateMethod method, int priority);
|
||||
|
||||
public void unregister(Updater updater, UpdateType type, UpdateMethod method);
|
||||
|
||||
public void start();
|
||||
|
||||
public void shutdown();
|
||||
|
||||
/**
|
||||
* Called by the Updater, either after check() was called, or it found out on its own.
|
||||
*
|
||||
* @param newsSource who told us
|
||||
* @param id plugin name for plugins, ignored otherwise
|
||||
* @param method How to get the new version
|
||||
* @param updateSourcew Where to get the new version
|
||||
* @param newVersion The new version available
|
||||
* @param minVersion The minimum installed version to be able to update to newVersion
|
||||
* @return true if we didn't know already
|
||||
*/
|
||||
public boolean notifyVersionAvailable(UpdateTask task, URI newsSource,
|
||||
UpdateType type, String id,
|
||||
UpdateMethod method, List<URI> updateSources,
|
||||
String newVersion, String minVersion);
|
||||
|
||||
/**
|
||||
* Called by the Updater after check() was called and all notifyVersionAvailable() callbacks are finished
|
||||
* @param newer notifyVersionAvailable was called
|
||||
* @param success check succeeded (newer or not)
|
||||
*/
|
||||
public void notifyCheckComplete(UpdateTask task, boolean newer, boolean success);
|
||||
|
||||
public void notifyProgress(UpdateTask task, String status);
|
||||
public void notifyProgress(UpdateTask task, String status, long downloaded, long totalSize);
|
||||
|
||||
/**
|
||||
* Not necessarily the end if there are more URIs to try.
|
||||
* @param t may be null
|
||||
*/
|
||||
public void notifyAttemptFailed(UpdateTask task, String reason, Throwable t);
|
||||
|
||||
/**
|
||||
* The task has finished and failed.
|
||||
* @param t may be null
|
||||
*/
|
||||
public void notifyTaskFailed(UpdateTask task, String reason, Throwable t);
|
||||
|
||||
/**
|
||||
* An update has been downloaded but not verified.
|
||||
* The manager will verify it.
|
||||
* Caller should delete the file upon return, unless it will share it with others,
|
||||
* e.g. on a torrent.
|
||||
*
|
||||
* @param actualVersion may be higher (or lower?) than the version requested
|
||||
* @param file a valid format for the task's UpdateType
|
||||
* @return true if valid, false if corrupt
|
||||
*/
|
||||
public boolean notifyComplete(UpdateTask task, String actualVersion, File file);
|
||||
}
|
15
core/java/src/net/i2p/update/UpdateMethod.java
Normal file
15
core/java/src/net/i2p/update/UpdateMethod.java
Normal file
@ -0,0 +1,15 @@
|
||||
package net.i2p.update;
|
||||
|
||||
/**
|
||||
* Transport mechanism for getting something.
|
||||
*
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public enum UpdateMethod {
|
||||
METHOD_DUMMY,
|
||||
HTTP, // .i2p or via outproxy
|
||||
HTTP_CLEARNET, // direct non-.i2p
|
||||
TORRENT,
|
||||
GNUTELLA, IMULE, TAHOE_LAFS,
|
||||
DEBIAN
|
||||
}
|
30
core/java/src/net/i2p/update/UpdateTask.java
Normal file
30
core/java/src/net/i2p/update/UpdateTask.java
Normal file
@ -0,0 +1,30 @@
|
||||
package net.i2p.update;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* A running check or download. Cannot be restarted.
|
||||
*
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public interface UpdateTask {
|
||||
|
||||
public void shutdown();
|
||||
|
||||
public boolean isRunning();
|
||||
|
||||
public UpdateType getType();
|
||||
|
||||
public UpdateMethod getMethod();
|
||||
|
||||
/**
|
||||
* The current URI being checked or downloaded from.
|
||||
* Can change if there are multiple URIs to try.
|
||||
*/
|
||||
public URI getURI();
|
||||
|
||||
/**
|
||||
* Valid for plugins
|
||||
*/
|
||||
public String getID();
|
||||
}
|
16
core/java/src/net/i2p/update/UpdateType.java
Normal file
16
core/java/src/net/i2p/update/UpdateType.java
Normal file
@ -0,0 +1,16 @@
|
||||
package net.i2p.update;
|
||||
|
||||
/**
|
||||
* What to update
|
||||
*
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public enum UpdateType {
|
||||
TYPE_DUMMY,
|
||||
NEWS,
|
||||
ROUTER_SIGNED,
|
||||
ROUTER_SIGNED_PACK200, // unused, use ROUTER_SIGNED for both
|
||||
ROUTER_UNSIGNED,
|
||||
PLUGIN, PLUGIN_INSTALL,
|
||||
GEOIP, BLOCKLIST, RESEED
|
||||
}
|
36
core/java/src/net/i2p/update/Updater.java
Normal file
36
core/java/src/net/i2p/update/Updater.java
Normal file
@ -0,0 +1,36 @@
|
||||
package net.i2p.update;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Controls one or more types of updates.
|
||||
* This must be registered with the UpdateManager.
|
||||
*
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public interface Updater {
|
||||
|
||||
/**
|
||||
* Check for updates.
|
||||
* Should not block.
|
||||
* If any are found, call back to UpdateManager.notifyUpdateAvailable().
|
||||
*
|
||||
* @param id plugin name or ignored
|
||||
* @param maxTime how long you have
|
||||
* @return active task or null if unable to check
|
||||
*/
|
||||
public UpdateTask check(UpdateType type, UpdateMethod method,
|
||||
String id, String currentVersion, long maxTime);
|
||||
|
||||
/**
|
||||
* Start a download and return a handle to the download task.
|
||||
* Should not block.
|
||||
*
|
||||
* @param id plugin name or ignored
|
||||
* @param maxTime how long you have
|
||||
* @return active task or null if unable to download
|
||||
*/
|
||||
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
|
||||
String id, String newVersion, long maxTime);
|
||||
}
|
8
core/java/src/net/i2p/update/package.html
Normal file
8
core/java/src/net/i2p/update/package.html
Normal file
@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Interfaces for classes to assist in the update process without
|
||||
needing the router context.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
@ -22,7 +22,7 @@ public class VersionComparator implements Comparator<String> {
|
||||
while (lTokens.hasMoreTokens() && rTokens.hasMoreTokens()) {
|
||||
String lNumber = lTokens.nextToken();
|
||||
String rNumber = rTokens.nextToken();
|
||||
int diff = intCompare(lNumber, rNumber);
|
||||
int diff = longCompare(lNumber, rNumber);
|
||||
if (diff != 0)
|
||||
return diff;
|
||||
}
|
||||
@ -34,19 +34,24 @@ public class VersionComparator implements Comparator<String> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static final int intCompare(String lop, String rop) {
|
||||
int left, right;
|
||||
private static final int longCompare(String lop, String rop) {
|
||||
long left, right;
|
||||
try {
|
||||
left = Integer.parseInt(lop);
|
||||
left = Long.parseLong(lop);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
right = Integer.parseInt(rop);
|
||||
right = Long.parseLong(rop);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return 1;
|
||||
}
|
||||
return left - right;
|
||||
long diff = left - right;
|
||||
if (diff < 0)
|
||||
return -1;
|
||||
if (diff > 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static final String VALID_SEPARATOR_CHARS = ".-_";
|
||||
|
@ -99,18 +99,23 @@
|
||||
</target>
|
||||
|
||||
<!-- unit tests -->
|
||||
<target name="builddepscalatest">
|
||||
<ant dir="../../core/java/" target="jar" />
|
||||
<ant dir="../../core/java/" target="jarScalaTest" />
|
||||
</target>
|
||||
<target name="builddeptest">
|
||||
<ant dir="../../core/java/" target="jarTest" />
|
||||
</target>
|
||||
<target name="scalatest.compileTest" depends="jar, scala.init">
|
||||
<target name="scalatest.compileTest" depends="builddepscalatest, compile, scala.init">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj_scala" />
|
||||
<scalac srcdir="./test/scalatest" destdir="./build/obj_scala" deprecation="on" >
|
||||
<classpath>
|
||||
<pathelement location="${classpath}" />
|
||||
<pathelement location="${scala-library.jar}" />
|
||||
<pathelement location="${scalatest.jar}" />
|
||||
<pathelement location="../../core/java/build/i2p.jar" />
|
||||
<pathelement location="./build/router.jar" />
|
||||
<pathelement location="../../core/java/build/i2pscalatest.jar" />
|
||||
<pathelement location="./build/obj" />
|
||||
</classpath>
|
||||
</scalac>
|
||||
</target>
|
||||
@ -124,6 +129,23 @@
|
||||
<compilerarg line="${javac.compilerargs}" />
|
||||
</javac>
|
||||
</target>
|
||||
<!-- jars with tests -->
|
||||
<target name="jarScalaTest" depends="scalatest.compileTest">
|
||||
<mkdir dir="./build/obj_scala_jar" />
|
||||
<copy todir="./build/obj_scala_jar">
|
||||
<fileset dir="./build/">
|
||||
<include name="obj/**/*.class"/>
|
||||
</fileset>
|
||||
<mapper type="glob" from="obj/*" to="*" />
|
||||
</copy>
|
||||
<copy todir="./build/obj_scala_jar">
|
||||
<fileset dir="./build/">
|
||||
<include name="obj_scala/**/*.class"/>
|
||||
</fileset>
|
||||
<mapper type="glob" from="obj_scala/*" to="*" />
|
||||
</copy>
|
||||
<jar destfile="./build/routerscalatest.jar" basedir="./build/obj_scala_jar" includes="**/*.class" />
|
||||
</target>
|
||||
<target name="jarTest" depends="junit.compileTest">
|
||||
<jar destfile="./build/routertest.jar" basedir="./build/obj" includes="**/*.class" />
|
||||
</target>
|
||||
|
@ -23,6 +23,7 @@ import net.i2p.router.transport.FIFOBandwidthLimiter;
|
||||
import net.i2p.router.transport.OutboundMessageRegistry;
|
||||
import net.i2p.router.tunnel.TunnelDispatcher;
|
||||
import net.i2p.router.tunnel.pool.TunnelPoolManager;
|
||||
import net.i2p.update.UpdateManager;
|
||||
import net.i2p.util.KeyRing;
|
||||
import net.i2p.util.I2PProperties.I2PPropertyCallback;
|
||||
|
||||
@ -56,11 +57,12 @@ public class RouterContext extends I2PAppContext {
|
||||
private Shitlist _shitlist;
|
||||
private Blocklist _blocklist;
|
||||
private MessageValidator _messageValidator;
|
||||
private UpdateManager _updateManager;
|
||||
//private MessageStateMonitor _messageStateMonitor;
|
||||
private RouterThrottle _throttle;
|
||||
private final Set<Runnable> _finalShutdownTasks;
|
||||
// split up big lock on this to avoid deadlocks
|
||||
private final Object _lock1 = new Object(), _lock2 = new Object();
|
||||
private final Object _lock1 = new Object(), _lock2 = new Object(), _lock3 = new Object();
|
||||
|
||||
private static final List<RouterContext> _contexts = new CopyOnWriteArrayList();
|
||||
|
||||
@ -483,6 +485,7 @@ public class RouterContext extends I2PAppContext {
|
||||
* @return true
|
||||
* @since 0.7.9
|
||||
*/
|
||||
@Override
|
||||
public boolean isRouterContext() {
|
||||
return true;
|
||||
}
|
||||
@ -492,7 +495,44 @@ public class RouterContext extends I2PAppContext {
|
||||
* @return the client manager
|
||||
* @since 0.8.3
|
||||
*/
|
||||
@Override
|
||||
public InternalClientManager internalClientManager() {
|
||||
return _clientManagerFacade;
|
||||
}
|
||||
|
||||
/**
|
||||
* The controller of router, plugin, and other updates.
|
||||
* @return The manager if it is registered, else null
|
||||
* @since 0.9.2
|
||||
*/
|
||||
@Override
|
||||
public UpdateManager updateManager() {
|
||||
return _updateManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register as the update manager.
|
||||
* @throws IllegalStateException if one was already registered
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public void registerUpdateManager(UpdateManager mgr) {
|
||||
synchronized(_lock3) {
|
||||
if (_updateManager != null)
|
||||
throw new IllegalStateException();
|
||||
_updateManager = mgr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the update manager.
|
||||
* @throws IllegalStateException if it was not registered
|
||||
* @since 0.9.2
|
||||
*/
|
||||
public void unregisterUpdateManager(UpdateManager mgr) {
|
||||
synchronized(_lock3) {
|
||||
if (_updateManager != mgr)
|
||||
throw new IllegalStateException();
|
||||
_updateManager = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user