From fea3bb63c1856414ea60b8ff0aa4cbc69bdf4869 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Fri, 19 Oct 2012 20:26:08 +0000
Subject: [PATCH] - Save available unsigned version across restarts - Fix
 status display after downloaded - Don't display update buttons unless HTTP
 proxy is up - Pass the manager down thru the constructors

---
 .../router/update/ConsoleUpdateManager.java   | 62 +++++++++----------
 .../net/i2p/router/update/DummyHandler.java   | 19 +++---
 .../net/i2p/router/update/NewsFetcher.java    |  4 +-
 .../net/i2p/router/update/NewsHandler.java    |  6 +-
 .../net/i2p/router/update/NewsTimerTask.java  |  4 +-
 .../router/update/PluginUpdateChecker.java    |  5 +-
 .../router/update/PluginUpdateHandler.java    | 10 ++-
 .../i2p/router/update/PluginUpdateRunner.java |  5 +-
 .../router/update/UnsignedUpdateChecker.java  |  5 +-
 .../router/update/UnsignedUpdateHandler.java  | 37 +++++++----
 .../router/update/UnsignedUpdateRunner.java   |  5 +-
 .../net/i2p/router/update/UpdateHandler.java  |  8 ++-
 .../net/i2p/router/update/UpdateRunner.java   |  8 +--
 .../src/net/i2p/router/web/NewsHelper.java    | 37 ++++++++++-
 .../src/net/i2p/router/web/SummaryHelper.java | 39 +++++++-----
 15 files changed, 160 insertions(+), 94 deletions(-)

diff --git a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
index 4f673b8b6b..1f680fbf6d 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
@@ -5,7 +5,6 @@ import java.io.File;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.text.DecimalFormat;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -77,6 +76,7 @@ public class ConsoleUpdateManager implements UpdateManager {
     private static final long DEFAULT_CHECK_TIME = 60*1000;
     private static final long STATUS_CLEAN_TIME = 20*60*1000;
     private static final long TASK_CLEANER_TIME = 15*60*1000;
+    private static final String PROP_UNSIGNED_AVAILABLE = "router.updateUnsignedAvailable";
 
     public ConsoleUpdateManager(RouterContext ctx) {
         _context = ctx;
@@ -99,7 +99,7 @@ public class ConsoleUpdateManager implements UpdateManager {
         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();
+        (new NewsFetcher(_context, this, Collections.EMPTY_LIST)).checkForUpdates();
         for (String plugin : PluginStarter.getPlugins()) {
             Properties props = PluginStarter.pluginProperties(_context, plugin);
             String ver = props.getProperty("version");
@@ -108,24 +108,32 @@ public class ConsoleUpdateManager implements UpdateManager {
         }
 
         _context.registerUpdateManager(this);
-        DummyHandler dh = new DummyHandler(_context);
+        DummyHandler dh = new DummyHandler(_context, this);
         register((Checker)dh, TYPE_DUMMY, HTTP, 0);
         register((Updater)dh, 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
-        Checker c = new NewsHandler(_context);
+        Checker c = new NewsHandler(_context, this);
         register(c, NEWS, HTTP, 0);
         register(c, ROUTER_SIGNED, HTTP, 0);  // news is an update checker for the router
-        Updater u = new UpdateHandler(_context);
+        Updater u = new UpdateHandler(_context, this);
         register(u, ROUTER_SIGNED, HTTP, 0);
-        UnsignedUpdateHandler uuh = new UnsignedUpdateHandler(_context);
+        UnsignedUpdateHandler uuh = new UnsignedUpdateHandler(_context, this);
         register((Checker)uuh, ROUTER_UNSIGNED, HTTP, 0);
         register((Updater)uuh, ROUTER_UNSIGNED, HTTP, 0);
-        PluginUpdateHandler puh = new PluginUpdateHandler(_context);
+        String newVersion = _context.getProperty(PROP_UNSIGNED_AVAILABLE);
+        if (newVersion != null) {
+            List<URI> updateSources = uuh.getUpdateSources();
+            if (uuh != null) {
+                VersionAvailable newVA = new VersionAvailable(newVersion, "", HTTP, updateSources);
+                _available.put(new UpdateItem(ROUTER_UNSIGNED, ""), newVA);
+            }
+        }
+        PluginUpdateHandler puh = new PluginUpdateHandler(_context, this);
         register((Checker)puh, PLUGIN, HTTP, 0);
         register((Checker)puh, PLUGIN_INSTALL, HTTP, 0);
         register((Updater)puh, PLUGIN_INSTALL, HTTP, 0);
-        new NewsTimerTask(_context);
+        new NewsTimerTask(_context, this);
         _context.simpleScheduler().addPeriodicEvent(new TaskCleaner(), TASK_CLEANER_TIME);
     }
 
@@ -605,9 +613,13 @@ public class ConsoleUpdateManager implements UpdateManager {
             case NEWS:
                 break;
 
+            case ROUTER_UNSIGNED:
+                // save across restarts
+                _context.router().saveConfig(PROP_UNSIGNED_AVAILABLE, newVersion);
+                // fall through
+
             case ROUTER_SIGNED:
             case ROUTER_SIGNED_PACK200:
-            case ROUTER_UNSIGNED:
                 if (shouldInstall() &&
                     !(isUpdateInProgress(ROUTER_SIGNED) ||
                       isUpdateInProgress(ROUTER_SIGNED_PACK200) ||
@@ -758,8 +770,10 @@ public class ConsoleUpdateManager implements UpdateManager {
 
             case ROUTER_UNSIGNED:
                 rv = handleUnsignedFile(task.getURI(), actualVersion, file);
-                if (rv)
+                if (rv) {
+                    _context.router().saveConfig(PROP_UNSIGNED_AVAILABLE, null);
                     notifyDownloaded(task.getType(), task.getID(), actualVersion);
+                }
                 break;
 
             case PLUGIN:
@@ -887,6 +901,7 @@ public class ConsoleUpdateManager implements UpdateManager {
     }
 
     /**
+     *
      *  @return success
      */
     private boolean handleSudFile(URI uri, String actualVersion, File f) {
@@ -911,16 +926,8 @@ public class ConsoleUpdateManager implements UpdateManager {
                 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());
+                // SummaryHelper will display restart info separately
+                updateStatus("");
             }
         } else {
             _log.log(Log.CRIT, err + " from " + url);
@@ -963,15 +970,8 @@ public class ConsoleUpdateManager implements UpdateManager {
                 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"));
-                String ver = (new SimpleDateFormat("dd-MMM HH:mm")).format(new Date(modtime)) + " UTC";
-                buf.append(' ').append(_("Version {0}", ver));
-                updateStatus(buf.toString());
+                // SummaryHelper will display restart info separately
+                updateStatus("");
             }
         } else {
             _log.log(Log.CRIT, "Failed copy to " + to);
@@ -999,14 +999,14 @@ public class ConsoleUpdateManager implements UpdateManager {
     }
 
     /** translate a string */
-    private String _(String s) {
+    public String _(String s) {
         return Messages.getString(s, _context);
     }
 
     /**
      *  translate a string with a parameter
      */
-    private String _(String s, Object o) {
+    public String _(String s, Object o) {
         return Messages.getString(s, o, _context);
     }
 
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/DummyHandler.java b/apps/routerconsole/java/src/net/i2p/router/update/DummyHandler.java
index 538e4c1b21..4071efd2ec 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/DummyHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/DummyHandler.java
@@ -14,9 +14,11 @@ import net.i2p.update.*;
  */
 class DummyHandler implements Checker, Updater {
     private final RouterContext _context;
+    private final ConsoleUpdateManager _mgr;
     
-    public DummyHandler(RouterContext ctx) {
+    public DummyHandler(RouterContext ctx, ConsoleUpdateManager mgr) {
         _context = ctx;
+        _mgr = mgr;
     }
 
     /**
@@ -26,7 +28,7 @@ class DummyHandler implements Checker, Updater {
                             String id, String currentVersion, long maxTime) {
         if (type != UpdateType.TYPE_DUMMY)
             return null;
-         return new DummyRunner(_context, maxTime);
+         return new DummyRunner(_context, _mgr, maxTime);
     }
 
     /**
@@ -36,7 +38,7 @@ class DummyHandler implements Checker, Updater {
                              String id, String newVersion, long maxTime) {
         if (type != UpdateType.TYPE_DUMMY)
             return null;
-         return new DummyRunner(_context, maxTime);
+         return new DummyRunner(_context, _mgr, maxTime);
     }
 
     /**
@@ -45,8 +47,8 @@ class DummyHandler implements Checker, Updater {
     private static class DummyRunner extends UpdateRunner {
         private final long _delay;
 
-        public DummyRunner(RouterContext ctx, long maxTime) {
-            super(ctx, Collections.EMPTY_LIST);
+        public DummyRunner(RouterContext ctx, ConsoleUpdateManager mgr, long maxTime) {
+            super(ctx, mgr, Collections.EMPTY_LIST);
             _delay = maxTime;
         }
 
@@ -58,11 +60,8 @@ class DummyHandler implements Checker, Updater {
             try {
                 Thread.sleep(_delay);
             } catch (InterruptedException ie) {}
-            UpdateManager mgr = _context.updateManager();
-            if (mgr != null) {
-                mgr.notifyCheckComplete(this, false, false);
-                mgr.notifyTaskFailed(this, "dummy", null);
-            }
+            _mgr.notifyCheckComplete(this, false, false);
+            _mgr.notifyTaskFailed(this, "dummy", null);
         }
     }
 }
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
index c791549d5b..346cf2d9aa 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
@@ -43,8 +43,8 @@ class NewsFetcher extends UpdateRunner {
 
     private static final String TEMP_NEWS_FILE = "news.xml.temp";
     
-    public NewsFetcher(RouterContext ctx, List<URI> uris) { 
-        super(ctx, uris);
+    public NewsFetcher(RouterContext ctx, ConsoleUpdateManager mgr, List<URI> uris) { 
+        super(ctx, mgr, 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);
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/NewsHandler.java b/apps/routerconsole/java/src/net/i2p/router/update/NewsHandler.java
index 0ff108f688..5a3ed7501f 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/NewsHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/NewsHandler.java
@@ -24,8 +24,8 @@ class NewsHandler extends UpdateHandler implements Checker {
     /** @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);
+    public NewsHandler(RouterContext ctx, ConsoleUpdateManager mgr) {
+        super(ctx, mgr);
     }
 
     /**
@@ -45,7 +45,7 @@ class NewsHandler extends UpdateHandler implements Checker {
         try {
             updateSources.add(new URI(BACKUP_NEWS_URL));
         } catch (URISyntaxException use) {}
-        UpdateRunner update = new NewsFetcher(_context, updateSources);
+        UpdateRunner update = new NewsFetcher(_context, _mgr, updateSources);
         update.start();
         return update;
     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/NewsTimerTask.java b/apps/routerconsole/java/src/net/i2p/router/update/NewsTimerTask.java
index ae1aadd813..51dd3b8ad4 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/NewsTimerTask.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/NewsTimerTask.java
@@ -43,10 +43,10 @@ class NewsTimerTask implements SimpleTimer.TimedEvent {
     private static final long INITIAL_DELAY = 5*60*1000;
     private static final long RUN_DELAY = 10*60*1000;
 
-    public NewsTimerTask(RouterContext ctx) {
+    public NewsTimerTask(RouterContext ctx, ConsoleUpdateManager mgr) {
         _context = ctx;
         _log = ctx.logManager().getLog(NewsTimerTask.class);
-        _mgr = (ConsoleUpdateManager) _context.updateManager();
+        _mgr = mgr;
         ctx.simpleScheduler().addPeriodicEvent(this,
                                              INITIAL_DELAY + _context.random().nextLong(INITIAL_DELAY),
                                              RUN_DELAY);
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateChecker.java b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateChecker.java
index 20e0fbc3c3..56dd2e8a3d 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateChecker.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateChecker.java
@@ -34,8 +34,9 @@ class PluginUpdateChecker extends UpdateRunner {
     private final String _appName;
     private final String _oldVersion;
 
-    public PluginUpdateChecker(RouterContext ctx, List<URI> uris, String appName, String oldVersion ) { 
-        super(ctx, uris);
+    public PluginUpdateChecker(RouterContext ctx, ConsoleUpdateManager mgr,
+                               List<URI> uris, String appName, String oldVersion ) { 
+        super(ctx, mgr, uris);
         _baos = new ByteArrayOutputStream(TrustedUpdate.HEADER_BYTES);
         if (!uris.isEmpty())
             _currentURI = uris.get(0);
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateHandler.java
index c34e606f83..d142cdff84 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateHandler.java
@@ -36,9 +36,11 @@ import net.i2p.util.VersionComparator;
  */
 class PluginUpdateHandler implements Checker, Updater {
     private final RouterContext _context;
+    private final ConsoleUpdateManager _mgr;
 
-    public PluginUpdateHandler(RouterContext ctx) {
+    public PluginUpdateHandler(RouterContext ctx, ConsoleUpdateManager mgr) {
         _context = ctx;
+        _mgr = mgr;
     }
     
     /** check a single plugin */
@@ -64,7 +66,7 @@ class PluginUpdateHandler implements Checker, Updater {
             return null;
         }
 
-        UpdateRunner update = new PluginUpdateChecker(_context, updateSources, appName, oldVersion);
+        UpdateRunner update = new PluginUpdateChecker(_context, _mgr, updateSources, appName, oldVersion);
         update.start();
         return update;
     }
@@ -84,7 +86,9 @@ class PluginUpdateHandler implements Checker, Updater {
             return null;
         }
 
-        UpdateRunner update = new PluginUpdateRunner(_context, updateSources, appName, oldVersion);
+        UpdateRunner update = new PluginUpdateRunner(_context, _mgr, updateSources, appName, oldVersion);
+        // set status before thread to ensure UI feedback
+        _mgr.notifyProgress(update, "<b>" + _mgr._("Updating") + "</b>");
         update.start();
         return update;
     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java
index d3e3c00301..03e385abe1 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java
@@ -53,8 +53,9 @@ class PluginUpdateRunner extends UpdateRunner {
     private static final String ZIP = XPI2P + ".zip";
     public static final String PLUGIN_DIR = PluginStarter.PLUGIN_DIR;
 
-    public PluginUpdateRunner(RouterContext ctx, List<URI> uris, String appName, String oldVersion ) { 
-        super(ctx, uris);
+    public PluginUpdateRunner(RouterContext ctx, ConsoleUpdateManager mgr, List<URI> uris,
+                              String appName, String oldVersion ) { 
+        super(ctx, mgr, uris);
         if (uris.isEmpty())
             _uri = null;
         else
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateChecker.java b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateChecker.java
index 033e2dcb51..fe0b1acd7e 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateChecker.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateChecker.java
@@ -27,8 +27,9 @@ class UnsignedUpdateChecker extends UpdateRunner {
 
     protected static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
 
-    public UnsignedUpdateChecker(RouterContext ctx, List<URI> uris, long lastUpdateTime) { 
-        super(ctx, uris);
+    public UnsignedUpdateChecker(RouterContext ctx, ConsoleUpdateManager mgr,
+                                 List<URI> uris, long lastUpdateTime) { 
+        super(ctx, mgr, uris);
         _ms = lastUpdateTime;
     }
 
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateHandler.java
index 7b733b26e8..a2a94f4c15 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateHandler.java
@@ -31,30 +31,41 @@ import net.i2p.util.Log;
  */
 class UnsignedUpdateHandler implements Checker, Updater {
     private final RouterContext _context;
+    private final ConsoleUpdateManager _mgr;
 
-    public UnsignedUpdateHandler(RouterContext ctx) {
+    public UnsignedUpdateHandler(RouterContext ctx, ConsoleUpdateManager mgr) {
         _context = ctx;
+        _mgr = mgr;
     }
 
     /**
-     *  @param currentVersion ignored, we use time stored in a property
+     *  @return null if none
+     *  @since 0.9.4
      */
-    @Override
-    public UpdateTask check(UpdateType type, UpdateMethod method,
-                            String id, String currentVersion, long maxTime) {
-        if (type != UpdateType.ROUTER_UNSIGNED || method != UpdateMethod.HTTP)
-            return null;
-
+    public List<URI> getUpdateSources() {
         String url = _context.getProperty(ConfigUpdateHandler.PROP_ZIP_URL);
         if (url == null)
             return null;
 
-        List<URI> updateSources;
         try {
-            updateSources = Collections.singletonList(new URI(url));
+            return Collections.singletonList(new URI(url));
         } catch (URISyntaxException use) {
             return null;
         }
+    }
+
+    /**
+     *  @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;
+
+        List<URI> updateSources = getUpdateSources();
+        if (updateSources == null)
+            return null;
 
         long ms = _context.getProperty(NewsHelper.PROP_LAST_UPDATE_TIME, 0L);
         if (ms <= 0) {
@@ -65,7 +76,7 @@ class UnsignedUpdateHandler implements Checker, Updater {
             return null;
         }
 
-        UpdateRunner update = new UnsignedUpdateChecker(_context, updateSources, ms);
+        UpdateRunner update = new UnsignedUpdateChecker(_context, _mgr, updateSources, ms);
         update.start();
         return update;
     }
@@ -83,7 +94,9 @@ class UnsignedUpdateHandler implements Checker, Updater {
                              String id, String newVersion, long maxTime) {
         if (type != ROUTER_UNSIGNED || method != HTTP || updateSources.isEmpty())
             return null;
-        UpdateRunner update = new UnsignedUpdateRunner(_context, updateSources);
+        UpdateRunner update = new UnsignedUpdateRunner(_context, _mgr, updateSources);
+        // set status before thread to ensure UI feedback
+        _mgr.notifyProgress(update, "<b>" + _mgr._("Updating") + "</b>");
         update.start();
         return update;
     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateRunner.java b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateRunner.java
index e9ac30ce8a..eb797b9a1b 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateRunner.java
@@ -24,8 +24,8 @@ import net.i2p.util.Log;
  */
 class UnsignedUpdateRunner extends UpdateRunner {
 
-    public UnsignedUpdateRunner(RouterContext ctx, List<URI> uris) { 
-        super(ctx, uris);
+    public UnsignedUpdateRunner(RouterContext ctx, ConsoleUpdateManager mgr, List<URI> uris) { 
+        super(ctx, mgr, uris);
         if (!uris.isEmpty())
             _currentURI = uris.get(0);
     }
@@ -39,7 +39,6 @@ class UnsignedUpdateRunner extends UpdateRunner {
         @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
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/update/UpdateHandler.java
index 447558c721..1e1d9efa1b 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UpdateHandler.java
@@ -38,9 +38,11 @@ import net.i2p.util.VersionComparator;
  */
 class UpdateHandler implements Updater {
     protected final RouterContext _context;
+    protected final ConsoleUpdateManager _mgr;
     
-    public UpdateHandler(RouterContext ctx) {
+    public UpdateHandler(RouterContext ctx, ConsoleUpdateManager mgr) {
         _context = ctx;
+        _mgr = mgr;
     }
     
     /**
@@ -56,7 +58,9 @@ class UpdateHandler implements Updater {
         if ((type != UpdateType.ROUTER_SIGNED && type != UpdateType.ROUTER_SIGNED_PACK200) ||
             method != UpdateMethod.HTTP || updateSources.isEmpty())
             return null;
-        UpdateRunner update = new UpdateRunner(_context, updateSources);
+        UpdateRunner update = new UpdateRunner(_context, _mgr, updateSources);
+        // set status before thread to ensure UI feedback
+        _mgr.notifyProgress(update, "<b>" + _mgr._("Updating") + "</b>");
         update.start();
         return update;
     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/UpdateRunner.java b/apps/routerconsole/java/src/net/i2p/router/update/UpdateRunner.java
index 192fdfbb7a..9aafb2eb9f 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UpdateRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UpdateRunner.java
@@ -49,12 +49,12 @@ class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusList
     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) { 
+    public UpdateRunner(RouterContext ctx, ConsoleUpdateManager mgr, List<URI> uris) { 
         super("Update Runner");
         setDaemon(true);
         _context = ctx;
         _log = ctx.logManager().getLog(getClass());
-        _mgr = (ConsoleUpdateManager) ctx.updateManager();
+        _mgr = mgr;
         _urls = uris;
         _updateFile = (new File(ctx.getTempDir(), "update" + ctx.random().nextInt() + ".tmp")).getAbsolutePath();
     }
@@ -226,13 +226,13 @@ class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusList
 
     /** translate a string */
     protected String _(String s) {
-        return Messages.getString(s, _context);
+        return _mgr._(s);
     }
 
     /**
      *  translate a string with a parameter
      */
     protected String _(String s, Object o) {
-        return Messages.getString(s, o, _context);
+        return _mgr._(s, o);
     }
 }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java
index cb6eb68b93..5e5fcecfe4 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java
@@ -50,6 +50,7 @@ public class NewsHelper extends ContentHelper {
     }
 
     /**
+     *  Will be false if already downloaded
      *  @since 0.9.4 moved from NewsFetcher
      */
     public static boolean isUpdateAvailable() {
@@ -59,6 +60,7 @@ public class NewsHelper extends ContentHelper {
     }
 
     /**
+     *  Available version, will be null if already downloaded
      *  @return null if none
      *  @since 0.9.4 moved from NewsFetcher
      */
@@ -69,6 +71,18 @@ public class NewsHelper extends ContentHelper {
     }
 
     /**
+     *  Already downloaded but not installed version
+     *  @return null if none
+     *  @since 0.9.4
+     */
+    public static String updateVersionDownloaded() {
+        ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
+        if (mgr == null) return null;
+        return mgr.getUpdateDownloaded(ROUTER_SIGNED);
+    }
+
+    /**
+     *  Will be false if already downloaded
      *  @since 0.9.4 moved from NewsFetcher
      */
     public static boolean isUnsignedUpdateAvailable() {
@@ -84,11 +98,30 @@ public class NewsHelper extends ContentHelper {
     public static String unsignedUpdateVersion() {
         ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
         if (mgr == null) return null;
-        String ver = mgr.getUpdateAvailable(ROUTER_UNSIGNED);
+        return formatUnsignedVersion(mgr.getUpdateAvailable(ROUTER_UNSIGNED));
+    }
+
+    /**
+     *  Already downloaded but not installed version
+     *  @return null if none
+     *  @since 0.9.4
+     */
+    public static String unsignedVersionDownloaded() {
+        ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
+        if (mgr == null) return null;
+        return formatUnsignedVersion(mgr.getUpdateDownloaded(ROUTER_UNSIGNED));
+    }
+
+    /**
+     *  Convert long date stamp to
+     *  '07-Jul 21:09 UTC' with month name in the system locale
+     *  @return null if ver = null
+     *  @since 0.9.4 moved from NewsFetcher
+     */
+    private static String formatUnsignedVersion(String ver) {
         if (ver != null) {
             try {
                 long modtime = Long.parseLong(ver);
-                // '07-Jul 21:09 UTC' with month name in the system locale
                 return (new SimpleDateFormat("dd-MMM HH:mm")).format(new Date(modtime)) + " UTC";
             } catch (NumberFormatException nfe) {}
         }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
index abc52aa654..1d04344419 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
@@ -27,6 +27,7 @@ import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
 import net.i2p.router.transport.ntcp.NTCPAddress;
 import net.i2p.stat.Rate;
 import net.i2p.stat.RateStat;
+import net.i2p.util.PortMapper;
 
 /**
  * Simple helper to query the appropriate router for data necessary to render
@@ -627,19 +628,19 @@ public class SummaryHelper extends HelperBase {
     }
 ********/
 
-    public boolean updateAvailable() { 
+    private boolean updateAvailable() { 
         return NewsHelper.isUpdateAvailable();
     }
 
-    public boolean unsignedUpdateAvailable() { 
+    private boolean unsignedUpdateAvailable() { 
         return NewsHelper.isUnsignedUpdateAvailable();
     }
 
-    public String getUpdateVersion() { 
+    private String getUpdateVersion() { 
         return NewsHelper.updateVersion();
     }
 
-    public String getUnsignedUpdateVersion() { 
+    private String getUnsignedUpdateVersion() { 
         return NewsHelper.unsignedUpdateVersion();
     }
 
@@ -654,15 +655,26 @@ public class SummaryHelper extends HelperBase {
         if (status.length() > 0) {
             buf.append("<h4>").append(status).append("</h4>\n");
         }
-        if (updateAvailable() || unsignedUpdateAvailable()) {
-            if (NewsHelper.isUpdateInProgress()) {
-                // nothing
-            } else if(
-                      // isDone() is always false for now, see UpdateHandler
-                      // ((!update.isDone()) &&
-                      getAction() == null &&
-                      getUpdateNonce() == null &&
-                      ConfigRestartBean.getRestartTimeRemaining() > 12*60*1000) {
+        String dver = NewsHelper.updateVersionDownloaded();
+        if (dver == null)
+            dver = NewsHelper.unsignedVersionDownloaded();
+        if (dver != null &&
+            !NewsHelper.isUpdateInProgress() &&
+            !_context.router().gracefulShutdownInProgress()) {
+            buf.append("<h4><b>").append(_("Update downloaded")).append("<br>");
+            if (_context.hasWrapper())
+                buf.append(_("Click Restart to install"));
+            else
+                buf.append(_("Click Shutdown and restart to install"));
+            buf.append(' ').append(_("Version {0}", dver));
+            buf.append("</b></h4>");
+        }
+        if ((updateAvailable() || unsignedUpdateAvailable()) &&
+            !NewsHelper.isUpdateInProgress() &&
+            !_context.router().gracefulShutdownInProgress() &&
+            _context.portMapper().getPort(PortMapper.SVC_HTTP_PROXY) > 0 &&  // assume using proxy for now
+            getAction() == null &&
+            getUpdateNonce() == null) {
                 long nonce = _context.random().nextLong();
                 String prev = System.getProperty("net.i2p.router.web.UpdateHandler.nonce");
                 if (prev != null)
@@ -686,7 +698,6 @@ public class SummaryHelper extends HelperBase {
                        .append("</button><br>\n");
                 }
                 buf.append("</form>\n");
-            }
         }
         return buf.toString();
     }
-- 
GitLab