propagate from branch 'i2p.i2p' (head 2da3b585b42d058e25909bc303d72277ae2463b5)

to branch 'i2p.i2p.zzz.update' (head ebbad994215dc2822e9a1776399864ed77a0e5a0)
This commit is contained in:
zzz
2012-10-14 22:42:00 +00:00
42 changed files with 3010 additions and 1251 deletions

View File

@ -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

View File

@ -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);
}
}
}
}

View File

@ -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) {}
}

View File

@ -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;
}
}

View File

@ -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, "");
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,7 @@
<html>
<body>
<p>
Classes to implement the update process.
</p>
</body>
</html>

View File

@ -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"));
}
/**

View File

@ -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 {

View File

@ -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('<', ' ');

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -20,7 +20,7 @@ public class LogsHelper extends HelperBase {
}
/** @since 0.8.13 */
static String jettyVersion() {
public static String jettyVersion() {
return Server.getVersion();
}

View File

@ -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&amp;consoleNonce=").append(consoleNonce).append("\">")
.append(Messages.getString("Hide news", _context));
} else {
buf.append(" <a href=\"/?news=1&amp;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) {}
}

View File

@ -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&amp;consoleNonce=").append(consoleNonce).append("\">")
.append(Messages.getString("Hide news", ctx));
} else {
buf.append(" <a href=\"/?news=1&amp;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;
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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());
}

View File

@ -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

View File

@ -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>");
}
}
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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>

View File

@ -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;
}
}

View 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);
}

View 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
}

View 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();
}

View 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
}

View 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);
}

View 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>

View File

@ -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 = ".-_";

View File

@ -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>

View File

@ -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;
}
}
}