ConsoleUpdateManager.java 35.74 KiB
package net.i2p.router.update;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext;
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.ConfigServiceHandler;
import net.i2p.router.web.ConfigUpdateHandler;
import net.i2p.router.web.Messages;
import net.i2p.router.web.NewsHelper;
import net.i2p.router.web.PluginStarter;
import net.i2p.update.*;
import static net.i2p.update.UpdateType.*;
import static net.i2p.update.UpdateMethod.*;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
import net.i2p.util.VersionComparator;
/**
* The central resource coordinating updates.
* This must be registered with the context.
*
* The UpdateManager starts and stops all updates,
* prevents multiple updates as appropriate,
* and controls notification to the user.
*
* @since 0.9.2
*/
public class ConsoleUpdateManager implements UpdateManager {
private final RouterContext _context;
private final Log _log;
/** registered checkers / updaters */
private final Collection<RegisteredUpdater> _registered;
/** active checking tasks */
private final Collection<UpdateTask> _checkers;
/** active updating tasks, pointing to the next ones to try */
private final Map<UpdateTask, List<RegisteredUpdater>> _downloaders;
/** as reported by checkers */
private final Map<UpdateItem, VersionAvailable> _available;
/** downloaded but NOT installed */
private final Map<UpdateItem, Version> _downloaded;
/** downloaded but NOT installed */
private final Map<UpdateItem, Version> _installed;
private final DecimalFormat _pct = new DecimalFormat("0.0%");
private static final VersionComparator _versionComparator = new VersionComparator();
private volatile String _status;
public ConsoleUpdateManager(RouterContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(ConsoleUpdateManager.class);
_registered = new ConcurrentHashSet();
_checkers = new ConcurrentHashSet();
_downloaders = new ConcurrentHashMap();
_available = new ConcurrentHashMap();
_downloaded = new ConcurrentHashMap();
_installed = new ConcurrentHashMap();
_status = "";
}
public static ConsoleUpdateManager getInstance() {
return (ConsoleUpdateManager) I2PAppContext.getGlobalContext().updateManager();
}
public void start() {
notifyInstalled(NEWS, "", Long.toString(NewsHelper.lastUpdated(_context)));
notifyInstalled(ROUTER_SIGNED, "", RouterVersion.VERSION);
// hack to init from the current news file... do this before we register Updaters
(new NewsFetcher(_context, Collections.EMPTY_LIST)).checkForUpdates();
for (String plugin : PluginStarter.getPlugins()) {
Properties props = PluginStarter.pluginProperties(_context, plugin);
String ver = props.getProperty("version");
if (ver != null)
notifyInstalled(PLUGIN, plugin, ver);
}
_context.registerUpdateManager(this);
Updater u = new DummyHandler(_context);
register(u, TYPE_DUMMY, HTTP, 0);
// register news before router, so we don't fire off an update
// right at instantiation if the news is already indicating a new version
u = new NewsHandler(_context);
register(u, NEWS, HTTP, 0);
register(u, ROUTER_SIGNED, HTTP, 0); // news is an update checker for the router
u = new UpdateHandler(_context);
register(u, ROUTER_SIGNED, HTTP, 0);
u = new UnsignedUpdateHandler(_context);
register(u, ROUTER_UNSIGNED, HTTP, 0);
u = new PluginUpdateHandler(_context);
register(u, PLUGIN, HTTP, 0);
register(u, PLUGIN_INSTALL, HTTP, 0);
new NewsTimerTask(_context);
}
public void shutdown() {
_context.unregisterUpdateManager(this);
stopChecks();
stopUpdates();
_registered.clear();
_available.clear();
_downloaded.clear();
_installed.clear();
}
/**
* The status on any update current or last finished.
* @return status or ""
*/
public String getStatus() {
return _status;
}
public String checkAvailable(UpdateType type, long maxWait) {
return checkAvailable(type, "", maxWait);
}
/**
* Is an update available?
* Blocking.
* @param maxWait max time to block
* @return new version or null if nothing newer is available
*/
public String checkAvailable(UpdateType type, String id, long maxWait) {
//// update too?
if (isCheckInProgress(type, id) || isUpdateInProgress(type, id)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Check or update already in progress for: " + type + ' ' + id);
return null;
}
for (RegisteredUpdater r : _registered) {
if (r.type == type) {
UpdateTask t = r.updater.check(type, r.method, id, "FIXME", maxWait);
if (t != null) {
synchronized(t) {
try {
t.wait(maxWait);
} catch (InterruptedException ie) {}
}
return getUpdateAvailable(type, id);
}
}
}
return null;
}
/**
* Fire off a checker task
* Non-blocking.
*/
public void check(UpdateType type, String id) {
if (isCheckInProgress(type, id)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Check or update already in progress for: " + type + ' ' + id);
return;
}
for (RegisteredUpdater r : _registered) {
if (r.type == type) {
/// fixme "" will put an entry in _available for everything grrrrr????
UpdateTask t = r.updater.check(type, r.method, id, "", 5*60*1000);
if (t != null)
break;
}
}
}
/**
* Is an update available?
* Non-blocking, returns result of last check or notification from an Updater
* @return new version or null if nothing newer is available
*/
public String getUpdateAvailable(UpdateType type) {
return getUpdateAvailable(type, "");
}
/**
* Is an update available?
* Non-blocking, returns result of last check or notification from an Updater
* @return new version or null if nothing newer is available
*/
public String getUpdateAvailable(UpdateType type, String id) {
Version v = _available.get(new UpdateItem(type, id));
if (v == null)
return null;
return v.version;
}
/**
* Is an update downloaded?
* Non-blocking, returns result of last download
* @return new version or null if nothing was downloaded
*/
public String getUpdateDownloaded(UpdateType type) {
return getUpdateDownloaded(type, "");
}
/**
* Is an update downloaded?
* Non-blocking, returns result of last download
* @return new version or null if nothing was downloaded
*/
public String getUpdateDownloaded(UpdateType type, String id) {
Version v = _downloaded.get(new UpdateItem(type, id));
if (v == null)
return null;
return v.version;
}
/**
* Is any download in progress?
* Does not include checks.
*/
public boolean isUpdateInProgress() {
return !_downloaders.isEmpty();
}
/**
* Is a download in progress?
*/
public boolean isUpdateInProgress(UpdateType type) {
return isUpdateInProgress(type, "");
}
/**
* Is a download in progress?
*/
public boolean isUpdateInProgress(UpdateType type, String id) {
for (UpdateTask t : _downloaders.keySet()) {
if (t.getType() == type && id.equals(t.getID()))
return true;
}
return false;
}
/**
* Stop all downloads in progress
*/
public void stopUpdates() {
for (UpdateTask t : _downloaders.keySet()) {
t.shutdown();
}
_downloaders.clear();
}
/**
* Stop this download
*/
public void stopUpdate(UpdateType type) {
stopUpdate(type, "");
}
/**
* Stop this download
*/
public void stopUpdate(UpdateType type, String id) {
for (Iterator<UpdateTask> iter = _downloaders.keySet().iterator(); iter.hasNext(); ) {
UpdateTask t = iter.next();
if (t.getType() == type && id.equals(t.getID())) {
iter.remove();
t.shutdown();
}
}
}
/**
* Is any check in progress?
* Does not include updates.
*/
public boolean isCheckInProgress() {
return !_checkers.isEmpty();
}
/**
* Is a check in progress?
*/
public boolean isCheckInProgress(UpdateType type) {
return isCheckInProgress(type, "");
}
/**
* Is a check in progress?
*/
public boolean isCheckInProgress(UpdateType type, String id) {
for (UpdateTask t : _checkers) {
if (t.getType() == type && id.equals(t.getID()))
return true;
}
return false;
}
/**
* Stop all checks in progress
*/
public void stopChecks() {
for (UpdateTask t : _checkers) {
t.shutdown();
}
_checkers.clear();
}
/**
* Stop this check
*/
public void stopCheck(UpdateType type) {
stopCheck(type, "");
}
/**
* Stop this check
*/
public void stopCheck(UpdateType type, String id) {
for (Iterator<UpdateTask> iter = _checkers.iterator(); iter.hasNext(); ) {
UpdateTask t = iter.next();
if (t.getType() == type && id.equals(t.getID())) {
iter.remove();
t.shutdown();
}
}
}
/**
* Install a plugin. Non-blocking.
* If returns true, then call isUpdateInProgress() in a loop
* @return true if task started
*/
public boolean installPlugin(URI uri) {
String fakeName = Long.toString(_context.random().nextLong());
List<URI> uris = Collections.singletonList(uri);
UpdateItem fake = new UpdateItem(PLUGIN_INSTALL, fakeName);
VersionAvailable va = new VersionAvailable("", "", HTTP, uris);
_available.put(fake, va);
return update(PLUGIN_INSTALL, fakeName);
}
/**
* Non-blocking. Does not check.
* If returns true, then call isUpdateInProgress() in a loop
* Max time 3 hours by default but not honored by all Updaters
* @return true if task started
*/
public boolean update(UpdateType type) {
return update(type, "", 3*60*1000);
}
/**
* Non-blocking. Does not check.
* Max time 3 hours by default but not honored by all Updaters
* If returns true, then call isUpdateInProgress() in a loop
* @return true if task started
*/
public boolean update(UpdateType type, String id) {
return update(type, id, 3*60*60*1000);
}
/**
* Non-blocking. Does not check.
* If returns true, then call isUpdateInProgress() in a loop
* @param maxTime not honored by all Updaters
* @return true if task started
*/
public boolean update(UpdateType type, long maxTime) {
return update(type, "", maxTime);
}
/**
* Non-blocking. Does not check.
* If returns true, then call isUpdateInProgress() in a loop
* @param maxTime not honored by all Updaters
* @return true if task started
*/
public boolean update(UpdateType type, String id, long maxTime) {
if (isCheckInProgress(type, id) || isUpdateInProgress(type, id)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Check or update already in progress for: " + type + ' ' + id);
return false;
}
List<URI> updateSources = null;
UpdateItem ui = new UpdateItem(type, id);
VersionAvailable va = _available.get(ui);
if (va == null)
return false;
List<RegisteredUpdater> sorted = new ArrayList(_registered);
Collections.sort(sorted);
return retry(ui, va.sourceMap, sorted, maxTime) != null;
}
private UpdateTask retry(UpdateItem ui,
Map<UpdateMethod, List<URI>> sourceMap,
List<RegisteredUpdater> toTry, long maxTime) {
for (Iterator<RegisteredUpdater> iter = toTry.iterator(); iter.hasNext(); ) {
RegisteredUpdater r = iter.next();
iter.remove();
// check in case unregistered later
if (!_registered.contains(r))
continue;
for (Map.Entry<UpdateMethod, List<URI>> e : sourceMap.entrySet()) {
UpdateMethod meth = e.getKey();
if (r.type == ui.type && r.method == meth) {
// fixme
UpdateTask t = r.updater.update(ui.type, meth, e.getValue(), ui.id, "", maxTime);
if (t != null) {
// race window here
// store the remaining ones for retrying
_downloaders.put(t, toTry);
return t;
}
}
}
}
return null;
}
/////////// start UpdateManager interface
/**
* Call multiple times, one for each type/method pair.
*/
public void register(Updater updater, UpdateType type, UpdateMethod method, int priority) {
RegisteredUpdater ru = new RegisteredUpdater(updater, type, method, priority);
if (_log.shouldLog(Log.INFO))
_log.info("Registering " + ru);
_registered.add(ru);
}
public void unregister(Updater updater, UpdateType type, UpdateMethod method) {
RegisteredUpdater ru = new RegisteredUpdater(updater, type, method, 0);
if (_log.shouldLog(Log.INFO))
_log.info("Unregistering " + ru);
_registered.remove(ru);
}
/**
* 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 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 it's newer
*/
public boolean notifyVersionAvailable(UpdateTask task, URI newsSource,
UpdateType type, String id,
UpdateMethod method, List<URI> updateSources,
String newVersion, String minVersion) {
if (type == NEWS) {
// shortcut
notifyInstalled(NEWS, "", newVersion);
return true;
}
UpdateItem ui = new UpdateItem(type, id);
VersionAvailable newVA = new VersionAvailable(newVersion, minVersion, method, updateSources);
Version old = _installed.get(ui);
if (_log.shouldLog(Log.INFO))
_log.info("notifyVersionAvailable " + ui + ' ' + newVA + " old: " + old);
if (old != null && old.compareTo(newVA) >= 0) {
if (_log.shouldLog(Log.WARN))
_log.warn(ui.toString() + ' ' + old + " already installed");
return false;
}
old = _downloaded.get(ui);
if (old != null && old.compareTo(newVA) >= 0) {
if (_log.shouldLog(Log.WARN))
_log.warn(ui.toString() + ' ' + old + " already downloaded");
return false;
}
VersionAvailable oldVA = _available.get(ui);
if (oldVA != null) {
int comp = oldVA.compareTo(newVA);
if (comp > 0) {
if (_log.shouldLog(Log.WARN))
_log.warn(ui.toString() + ' ' + oldVA + " already available");
return false;
}
if (comp == 0) {
if (oldVA.sourceMap.putIfAbsent(method, updateSources) == null) {
if (_log.shouldLog(Log.WARN))
_log.warn(ui.toString() + ' ' + oldVA + " updated with new source method");
} else {
if (_log.shouldLog(Log.WARN))
_log.warn(ui.toString() + ' ' + oldVA + " already available");
}
return false;
}
}
if (_log.shouldLog(Log.INFO))
_log.info(ui.toString() + ' ' + newVA + " now available");
_available.put(ui, newVA);
switch (type) {
case NEWS:
break;
case ROUTER_SIGNED:
case ROUTER_SIGNED_PACK200:
if (shouldInstall()) {
////////////
}
break;
case ROUTER_UNSIGNED:
if (shouldInstall()) {
////////////
}
break;
case PLUGIN:
String msg = "<b>" + _("New plugin version {0} is available", newVersion) + "</b>";
finishStatus(msg);
break;
default:
break;
}
return true;
// TODO
}
/**
* Called by the Updater after check() was called and all notifyVersionAvailable() callbacks are finished
*/
public void notifyCheckComplete(UpdateTask task, boolean newer, boolean success) {
if (_log.shouldLog(Log.INFO))
_log.info(task.toString() + " complete");
_checkers.remove(task);
switch (task.getType()) {
case NEWS:
// NewsFetcher will notify and spin off update tasks
break;
case ROUTER_SIGNED:
case ROUTER_SIGNED_PACK200:
break;
case ROUTER_UNSIGNED:
// if _mgr.getUpdateDownloaded(ROUTER_SIGNED) != null;
break;
case PLUGIN:
String msg = null;
if (!success)
msg = "<b>" + _("Update check failed for plugin {0}", task.getID()) + "</b>";
else if (!newer)
msg = "<b>" + _("No new version is available for plugin {0}", task.getID()) + "</b>";
/// else success.... message for that?
if (msg != null)
finishStatus(msg);
break;
default:
break;
}
synchronized(task) {
task.notifyAll();
}
// TODO
}
public void notifyProgress(UpdateTask task, String status, long downloaded, long totalSize) {
StringBuilder buf = new StringBuilder(64);
buf.append(status).append(' ');
double pct = ((double)downloaded) / ((double)totalSize);
synchronized (_pct) {
buf.append(_pct.format(pct));
}
buf.append("<br>\n");
buf.append(_("{0}B transferred", DataHelper.formatSize2(downloaded)));
updateStatus(buf.toString());
}
/**
* @param task may be null
*/
public void notifyProgress(UpdateTask task, String status) {
updateStatus(status);
}
/**
* An expiring status
* @param task may be null
*/
public void notifyComplete(UpdateTask task, String status) {
finishStatus(status);
}
/**
* 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) {
_log.warn("Attempt failed " + task + ": " + reason, t);
}
/**
* The task has finished and failed.
* @param t may be null
*/
public void notifyTaskFailed(UpdateTask task, String reason, Throwable t) {
if (_log.shouldLog(Log.WARN))
_log.warn("Failed " + task + ": " + reason, t);
List<RegisteredUpdater> toTry = _downloaders.get(task);
if (toTry != null) {
UpdateItem ui = new UpdateItem(task.getType(), task.getID());
VersionAvailable va = _available.get(ui);
if (va != null) {
UpdateTask next = retry(ui, va.sourceMap, toTry, 3*60*1000); // fixme old maxtime lost
if (next != null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Retrying with " + next);
}
}
}
_downloaders.remove(task);
///// for certain types only
finishStatus("<b>" + _("Transfer failed from {0}", linkify(task.getURI().toString())) + "</b>");
}
/**
* 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.
* If the return value is false, caller must call notifyTaskFailed() or notifyComplete()
* again.
*
* @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) {
if (_log.shouldLog(Log.INFO))
_log.info(task.toString() + " complete");
boolean rv = false;
switch (task.getType()) {
case TYPE_DUMMY:
case NEWS:
rv = true;
break;
case ROUTER_SIGNED:
case ROUTER_SIGNED_PACK200:
rv = handleSudFile(task.getURI(), actualVersion, file);
if (rv)
notifyDownloaded(task.getType(), task.getID(), actualVersion);
break;
case ROUTER_UNSIGNED:
rv = handleUnsignedFile(task.getURI(), actualVersion, file);
/////// FIXME RFC822 or long?
if (rv)
notifyDownloaded(task.getType(), task.getID(), actualVersion);
break;
case PLUGIN:
/// FIXME probably handled in PluginUpdateRunner??????????
rv = handlePluginFile(task.getURI(), actualVersion, file);
break;
default:
break;
}
if (rv)
_downloaders.remove(task);
return rv;
}
///////// End UpdateManager interface
/**
* Adds to installed, removes from downloaded and available
* @param version null to remove from installed
*/
private void notifyInstalled(UpdateType type, String id, String version) {
UpdateItem ui = new UpdateItem(type, id);
if (version == null) {
_installed.remove(ui);
if (_log.shouldLog(Log.INFO))
_log.info(ui + " removed");
return;
}
Version ver = new Version(version);
if (_log.shouldLog(Log.INFO))
_log.info(ui + " " + ver + " installed");
_installed.put(ui, ver);
Version old = _downloaded.get(ui);
if (old != null && old.compareTo(ver) <= 0)
_downloaded.remove(ui);
old = _available.get(ui);
if (old != null && old.compareTo(ver) <= 0)
_available.remove(ui);
}
/**
* Adds to downloaded, removes from available
*/
private void notifyDownloaded(UpdateType type, String id, String version) {
UpdateItem ui = new UpdateItem(type, id);
Version ver = new Version(version);
if (_log.shouldLog(Log.INFO))
_log.info(ui + " " + ver + " downloaded");
_downloaded.put(ui, ver);
// one trumps the other
if (type == ROUTER_SIGNED)
_downloaded.remove(new UpdateItem(ROUTER_UNSIGNED, ""));
else if (type == ROUTER_UNSIGNED)
_downloaded.remove(new UpdateItem(ROUTER_SIGNED, ""));
Version old = _available.get(ui);
if (old != null && old.compareTo(ver) <= 0)
_available.remove(ui);
}
/** from NewsFetcher */
private boolean shouldInstall() {
String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
if ("notify".equals(policy) || NewsHelper.dontInstall(_context))
return false;
//////////////////
File zip = new File(_context.getRouterDir(), Router.UPDATE_FILE);
return !zip.exists();
}
/**
* Where to find various resources
* @return non-null may be empty
*/
public List<URI> getUpdateURLs(UpdateType type, String id, UpdateMethod method) {
VersionAvailable va = _available.get(new UpdateItem(type, id));
if (va != null) {
List<URI> rv = va.sourceMap.get(method);
if (rv != null)
return rv;
}
switch (type) {
case NEWS:
// handled in NewsHandler
break;
case ROUTER_SIGNED:
case ROUTER_SIGNED_PACK200:
String URLs = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL, ConfigUpdateHandler.DEFAULT_UPDATE_URL);
StringTokenizer tok = new StringTokenizer(URLs, " ,\r\n");
List<URI> rv = new ArrayList();
while (tok.hasMoreTokens()) {
try {
rv.add(new URI(tok.nextToken().trim()));
} catch (URISyntaxException use) {}
}
Collections.shuffle(rv, _context.random());
return rv;
case ROUTER_UNSIGNED:
String url = _context.getProperty(ConfigUpdateHandler.PROP_ZIP_URL);
if (url != null) {
try {
return Collections.singletonList(new URI(url));
} catch (URISyntaxException use) {}
}
break;
case PLUGIN:
Properties props = PluginStarter.pluginProperties(_context, id);
String oldVersion = props.getProperty("version");
String xpi2pURL = props.getProperty("updateURL");
if (xpi2pURL != null) {
try {
return Collections.singletonList(new URI(xpi2pURL));
} catch (URISyntaxException use) {}
}
break;
default:
break;
}
return Collections.EMPTY_LIST;
}
/**
* @return success
*/
private boolean handleSudFile(URI uri, String actualVersion, File f) {
String url = uri.toString();
// Process the .sud/.su2 file
updateStatus("<b>" + _("Update downloaded") + "</b>");
TrustedUpdate up = new TrustedUpdate(_context);
File to = new File(_context.getRouterDir(), Router.UPDATE_FILE);
String err = up.migrateVerified(RouterVersion.VERSION, f, to);
///////////
// caller must delete now.. why?
//f.delete();
if (err == null) {
String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
// So unsigned update handler doesn't overwrite unless newer.
/// FIXME
//String lastmod = _get.getLastModified();
String lastmod = null;
long modtime = 0;
if (lastmod != null)
modtime = RFC822Date.parse822Date(lastmod);
if (modtime <= 0)
modtime = _context.clock().now();
_context.router().saveConfig(NewsHelper.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>");
}
return err == null;
}
/**
* @return success
*/
private boolean handleUnsignedFile(URI uri, String lastmod, File updFile) {
if (FileUtil.verifyZip(updFile)) {
updateStatus("<b>" + _("Update downloaded") + "</b>");
} else {
updFile.delete();
String url = uri.toString();
updateStatus("<b>" + _("Unsigned update file from {0} is corrupt", url) + "</b>");
_log.log(Log.CRIT, "Corrupt zip file from " + url);
return false;
}
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);
long modtime = 0;
if (lastmod != null)
modtime = RFC822Date.parse822Date(lastmod);
if (modtime <= 0)
modtime = _context.clock().now();
_context.router().saveConfig(NewsHelper.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"));
/// OK?
buf.append(' ').append(_("Version {0}", lastmod));
updateStatus(buf.toString());
}
} else {
_log.log(Log.CRIT, "Failed copy to " + to);
updateStatus("<b>" + _("Failed copy to {0}", to.getAbsolutePath()) + "</b>");
}
return copied;
}
/**
* @return success
*/
private boolean handlePluginFile(URI uri, String actualVersion, File sudFile) {
//////////////// handled elsewhere?
return false;
}
private void restart() {
if (_context.hasWrapper())
ConfigServiceHandler.registerWrapperNotifier(_context, Router.EXIT_GRACEFUL_RESTART, false);
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
}
static String linkify(String url) {
return "<a target=\"_blank\" href=\"" + url + "\"/>" + url + "</a>";
}
/** translate a string */
private String _(String s) {
return Messages.getString(s, _context);
}
/**
* translate a string with a parameter
*/
private String _(String s, Object o) {
return Messages.getString(s, o, _context);
}
private void updateStatus(String s) {
_status = s;
}
private void finishStatus(String msg) {
updateStatus(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("");
}
}
/**
* Equals on updater, type and method only
*/
private static class RegisteredUpdater implements Comparable<RegisteredUpdater> {
public final Updater updater;
public final UpdateType type;
public final UpdateMethod method;
public final int priority;
public RegisteredUpdater(Updater u, UpdateType t, UpdateMethod m, int priority) {
updater = u; type = t; method = m; this.priority = priority;
}
/** reverse, highest priority first, ensure different ones are different */
public int compareTo(RegisteredUpdater r) {
int p = r.priority - priority;
if (p != 0)
return p;
return hashCode() - r.hashCode();
}
@Override
public int hashCode() {
return updater.hashCode() ^ type.hashCode() ^ method.hashCode();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof RegisteredUpdater))
return false;
RegisteredUpdater r = (RegisteredUpdater) o;
return type == r.type && method == r.method &&
updater.equals(r.updater);
}
@Override
public String toString() {
return "RegisteredUpdater " + updater + " for " + type + ' ' + method + " @pri " + priority;
}
}
/**
* Equals on type and ID only
*/
private static class UpdateItem {
public final UpdateType type;
public final String id;
public UpdateItem(UpdateType t, String id) {
type = t;
this.id = id;
}
@Override
public int hashCode() {
return type.hashCode() ^ id.hashCode();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof UpdateItem))
return false;
UpdateItem r = (UpdateItem) o;
return type == r.type && id.equals(r.id);
}
@Override
public String toString() {
return "UpdateItem " + type + ' ' + id;
}
}
private static class Version implements Comparable<Version> {
public final String version;
public Version(String version) {
this.version = version;
}
public int compareTo(Version r) {
return _versionComparator.compare(version, r.version);
}
@Override
public String toString() {
return "Version " + version;
}
}
private static class VersionAvailable extends Version {
public final String minVersion;
public final ConcurrentHashMap<UpdateMethod, List<URI>> sourceMap;
/**
* Puts the method and sources in the map. The map may be added to later.
*/
public VersionAvailable(String version, String min, UpdateMethod method, List<URI> updateSources) {
super(version);
minVersion = min;
sourceMap = new ConcurrentHashMap(4);
sourceMap.put(method, updateSources);
}
@Override
public String toString() {
return "VersionAvailable " + version + ' ' + sourceMap;
}
}
}