package net.i2p.router.web; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.crypto.TrustedUpdate; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; import net.i2p.util.EepGet; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; 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 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 */ public void update() { Thread t = new I2PAppThread(new AllCheckerRunner(), "AllAppChecker", true); t.start(); } public class AllCheckerRunner implements Runnable { public void run() { List 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("" + _("Cannot check, plugin {0} is not installed", appName) + ""); return; } if (_pluginUpdateCheckerRunner == null) _pluginUpdateCheckerRunner = new PluginUpdateCheckerRunner(); if (_pluginUpdateCheckerRunner.isRunning()) return; _xpi2pURL = xpi2pURL; _appName = appName; _oldVersion = oldVersion; System.setProperty(PROP_UPDATE_IN_PROGRESS, "true"); I2PAppThread update = new I2PAppThread(_pluginUpdateCheckerRunner, "AppChecker", true); update.start(); } } public boolean isRunning() { return _pluginUpdateCheckerRunner != null && _pluginUpdateCheckerRunner.isRunning(); } @Override public boolean isDone() { // FIXME return false; } private void scheduleStatusClean(String msg) { SimpleScheduler.getInstance().addEvent(new Cleaner(msg), 60*60*1000); } private class Cleaner implements SimpleTimer.TimedEvent { private 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() { updateStatus("" + _("Checking for update of plugin {0}", _appName) + ""); // use the same settings as for updater 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); _baos.reset(); try { _get = new PartialEepGet(_context, proxyHost, proxyPort, _baos, _xpi2pURL, TrustedUpdate.HEADER_BYTES); _get.addStatusListener(PluginUpdateCheckerRunner.this); _get.fetch(); } 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) { String newVersion = TrustedUpdate.getVersionString(new ByteArrayInputStream(_baos.toByteArray())); boolean newer = (new VersionComparator()).compare(newVersion, _oldVersion) > 0; String msg; if (newer) msg = "" + _("New plugin version {0} is available", newVersion) + ""; else msg = "" + _("No new version is available for plugin {0}", _appName) + ""; 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 = "" + _("Update check failed for plugin {0}", _appName) + ""; updateStatus(msg); scheduleStatusClean(msg); } } }