From 0b4401e64b45988ad2f1a8da8586e4d32b709594 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Thu, 18 Oct 2012 14:28:14 +0000
Subject: [PATCH] - Lots of fixes for notifying when updates and checks are
 complete - Fixes for NewsHelper stored timestamps - Add getProperty(String,
 long) to context for sanity - New methods and types - Logging improvements -
 Add failsafe TaskCleaner

---
 .../router/update/ConsoleUpdateManager.java   | 60 +++++++++++++++---
 .../net/i2p/router/update/NewsFetcher.java    | 22 +++++--
 .../net/i2p/router/update/NewsHandler.java    |  8 ++-
 .../router/update/PluginUpdateChecker.java    | 11 +++-
 .../router/update/UnsignedUpdateChecker.java  |  2 +-
 .../router/update/UnsignedUpdateHandler.java  | 17 ++---
 .../router/update/UnsignedUpdateRunner.java   |  3 +-
 .../net/i2p/router/update/UpdateRunner.java   |  3 +
 .../i2p/router/web/ConfigUpdateHandler.java   |  8 ++-
 .../src/net/i2p/router/web/NewsHelper.java    | 63 +++++++------------
 core/java/src/net/i2p/I2PAppContext.java      | 20 ++++++
 .../java/src/net/i2p/update/UpdateMethod.java |  1 +
 core/java/src/net/i2p/update/UpdateType.java  |  3 +-
 13 files changed, 145 insertions(+), 76 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 4873c920c5..07f31b7a97 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
@@ -75,6 +75,8 @@ public class ConsoleUpdateManager implements UpdateManager {
 
     private static final long DEFAULT_MAX_TIME = 3*60*60*1000L;
     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;
 
     public ConsoleUpdateManager(RouterContext ctx) {
         _context = ctx;
@@ -124,6 +126,7 @@ public class ConsoleUpdateManager implements UpdateManager {
         register((Checker)puh, PLUGIN_INSTALL, HTTP, 0);
         register((Updater)puh, PLUGIN_INSTALL, HTTP, 0);
         new NewsTimerTask(_context);
+        _context.simpleScheduler().addPeriodicEvent(new TaskCleaner(), TASK_CLEANER_TIME);
     }
 
     public void shutdown() {
@@ -167,8 +170,11 @@ public class ConsoleUpdateManager implements UpdateManager {
                 UpdateTask t;
                 synchronized(_activeCheckers) {
                     t = r.checker.check(type, r.method, id, current, maxWait);
-                    if (t != null)
+                    if (t != null) {
+                         if (_log.shouldLog(Log.INFO))
+                             _log.info("Starting " + r);
                         _activeCheckers.add(t);
+                    }
                 }
                 if (t != null) {
                     synchronized(t) {
@@ -198,7 +204,7 @@ public class ConsoleUpdateManager implements UpdateManager {
     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);
+                _log.warn("Check already in progress for: " + type + ' ' + id);
             return;
         }
         for (RegisteredChecker r : _registeredCheckers) {
@@ -207,6 +213,8 @@ public class ConsoleUpdateManager implements UpdateManager {
                 synchronized(_activeCheckers) {
                     UpdateTask t = r.checker.check(type, r.method, id, current, DEFAULT_CHECK_TIME);
                     if (t != null) {
+                         if (_log.shouldLog(Log.INFO))
+                             _log.info("Starting " + r);
                         _activeCheckers.add(t);
                         break;
                     }
@@ -489,6 +497,8 @@ public class ConsoleUpdateManager implements UpdateManager {
                     if (t != null) {
                         // race window here
                         //  store the remaining ones for retrying
+                         if (_log.shouldLog(Log.INFO))
+                             _log.info("Starting " + r);
                         _downloaders.put(t, toTry);
                         return t;
                     }
@@ -624,7 +634,7 @@ public class ConsoleUpdateManager implements UpdateManager {
      */
     public void notifyCheckComplete(UpdateTask task, boolean newer, boolean success) {
         if (_log.shouldLog(Log.INFO))
-            _log.info(task.toString() + " complete");
+            _log.info("Checker " + task + " for " + task.getType() + " complete");
         synchronized(_activeCheckers) {
             _activeCheckers.remove(task);
         }
@@ -688,7 +698,8 @@ public class ConsoleUpdateManager implements UpdateManager {
      *  @param t may be null
      */
     public void notifyAttemptFailed(UpdateTask task, String reason, Throwable t) {
-        _log.warn("Attempt failed " + task + ": " + reason, t);
+        if (_log.shouldLog(Log.WARN))
+            _log.warn("Attempt failed " + task + " for " + task.getType() + ": " + reason, t);
     }
 
     /**
@@ -697,7 +708,7 @@ public class ConsoleUpdateManager implements UpdateManager {
      */
     public void notifyTaskFailed(UpdateTask task, String reason, Throwable t) {
         if (_log.shouldLog(Log.WARN))
-            _log.warn("Failed " + task + ": " + reason, t);
+            _log.warn("Failed " + task + " for " + task.getType() + ": " + reason, t);
         List<RegisteredUpdater> toTry = _downloaders.get(task);
         if (toTry != null) {
             UpdateItem ui = new UpdateItem(task.getType(), task.getID());
@@ -711,6 +722,7 @@ public class ConsoleUpdateManager implements UpdateManager {
             }
         }
         _downloaders.remove(task);
+        _activeCheckers.remove(task);
 ///// for certain types only
         finishStatus("<b>" + _("Transfer failed from {0}", linkify(task.getURI().toString())) + "</b>");
     }
@@ -729,7 +741,7 @@ public class ConsoleUpdateManager implements UpdateManager {
      */
     public boolean notifyComplete(UpdateTask task, String actualVersion, File file) {
         if (_log.shouldLog(Log.INFO))
-            _log.info(task.toString() + " complete");
+            _log.info("Updater " + task + " for " + task.getType() + " complete");
         boolean rv = false;
         switch (task.getType()) {
             case TYPE_DUMMY:
@@ -1004,12 +1016,12 @@ public class ConsoleUpdateManager implements UpdateManager {
 
     private void finishStatus(String msg) {
         updateStatus(msg);
-        _context.simpleScheduler().addEvent(new Cleaner(msg), 20*60*1000);
+        _context.simpleScheduler().addEvent(new StatusCleaner(msg), STATUS_CLEAN_TIME);
     }
 
-    private class Cleaner implements SimpleTimer.TimedEvent {
+    private class StatusCleaner implements SimpleTimer.TimedEvent {
         private final String _msg;
-        public Cleaner(String msg) {
+        public StatusCleaner(String msg) {
             _msg = msg;
         }
         public void timeReached() {
@@ -1018,6 +1030,36 @@ public class ConsoleUpdateManager implements UpdateManager {
         }
     }
 
+    /**
+     *  Failsafe
+     */
+    private class TaskCleaner implements SimpleTimer.TimedEvent {
+        public void timeReached() {
+            if (!_activeCheckers.isEmpty()) {
+                synchronized(_activeCheckers) {
+                    for (Iterator<UpdateTask> iter = _activeCheckers.iterator(); iter.hasNext(); ) {
+                        UpdateTask t = iter.next();
+                        if (!t.isRunning()) {
+                            if (_log.shouldLog(Log.WARN))
+                                _log.warn("Failsafe remove checker " + t);
+                            iter.remove();
+                        }
+                    }
+                }
+            }
+            if (!_downloaders.isEmpty()) {
+                for (Iterator<UpdateTask> iter = _downloaders.keySet().iterator(); iter.hasNext(); ) {
+                    UpdateTask t = iter.next();
+                    if (!t.isRunning()) {
+                        if (_log.shouldLog(Log.WARN))
+                            _log.warn("Failsafe remove downloader " + t);
+                        iter.remove();
+                    }
+                }
+            }
+        }
+    }
+
     /**
      *  Equals on updater, type and method only
      */
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 8de344e89a..e994304467 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
@@ -37,6 +37,9 @@ class NewsFetcher extends UpdateRunner {
     private String _lastModified;
     private final File _newsFile;
     private final File _tempFile;
+    /** is the news newer */
+    private boolean _isNewer;
+    private boolean _success;
 
     private static final String TEMP_NEWS_FILE = "news.xml.temp";
     
@@ -65,8 +68,14 @@ class NewsFetcher extends UpdateRunner {
     }
     
     @Override
-    public void update() {
-        fetchNews();
+    public void run() {
+        _isRunning = true;
+        try {
+            fetchNews();
+        } finally {
+            _mgr.notifyCheckComplete(this, _isNewer, _success);
+            _isRunning = false;
+        }
     }
 
     public void fetchNews() {
@@ -93,8 +102,9 @@ class NewsFetcher extends UpdateRunner {
                     if (lastMod != null) {
                         _lastModified = lastMod;
                         long lm = RFC822Date.parse822Date(lastMod);
-                        if (lm > 0)
-                            _context.router().saveConfig(NewsHelper.PROP_LAST_CHECKED, Long.toString(lm));
+                        if (lm == 0)
+                            lm = _context.clock().now();
+                        _context.router().saveConfig(NewsHelper.PROP_LAST_CHECKED, Long.toString(lm));
                     }
                     return;
                 }
@@ -107,8 +117,6 @@ class NewsFetcher extends UpdateRunner {
     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,
@@ -181,6 +189,7 @@ class NewsFetcher extends UpdateRunner {
                 _context.router().saveConfig(NewsHelper.PROP_LAST_UPDATED, newVer);
                 _mgr.notifyVersionAvailable(this, _currentURI, NEWS, "", HTTP,
                                             null, newVer, "");
+                _isNewer = true;
                 checkForUpdates();
             } else {
                 if (_log.shouldLog(Log.ERROR))
@@ -190,6 +199,7 @@ class NewsFetcher extends UpdateRunner {
             if (_log.shouldLog(Log.WARN))
                 _log.warn("Transfer complete, but no file? - probably 304 Not Modified");
         }
+        _success = true;
     }
 
     /** override to prevent status update */
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 fe4ca4304d..0ff108f688 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/NewsHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/NewsHandler.java
@@ -8,11 +8,15 @@ import java.util.List;
 import net.i2p.router.RouterContext;
 import net.i2p.router.web.ConfigUpdateHelper;
 import net.i2p.update.*;
+import static net.i2p.update.UpdateType.*;
+import static net.i2p.update.UpdateMethod.*;
 
 /**
  * Task to periodically look for updates to the news.xml, and to keep
  * track of whether that has an announcement for a new version.
  *
+ * Overrides UpdateRunner for convenience, this is not an Updater
+ *
  * @since 0.9.4 moved from NewsFetcher
  */
 class NewsHandler extends UpdateHandler implements Checker {
@@ -31,8 +35,8 @@ class NewsHandler extends UpdateHandler implements Checker {
      */
     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)
+        if ((type != ROUTER_SIGNED && type != ROUTER_SIGNED_PACK200 && type != NEWS) ||
+            method != HTTP)
             return null;
         List<URI> updateSources = new ArrayList(2);
         try {
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 3ae282f5b5..20e0fbc3c3 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateChecker.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateChecker.java
@@ -43,9 +43,18 @@ class PluginUpdateChecker extends UpdateRunner {
         _oldVersion = oldVersion;
     }
 
-
     @Override
     public UpdateType getType() { return UpdateType.PLUGIN; }
+    
+    @Override
+    public void run() {
+        _isRunning = true;
+        try {
+            update();
+        } finally {
+            _isRunning = false;
+        }
+    }
 
         @Override
         protected void update() {
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 62363da148..033e2dcb51 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateChecker.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateChecker.java
@@ -46,9 +46,9 @@ class UnsignedUpdateChecker extends UpdateRunner {
         try {
             success = fetchUnsignedHead();
         } finally {
+            _mgr.notifyCheckComplete(this, _unsignedUpdateAvailable, success);
             _isRunning = false;
         }
-        _mgr.notifyCheckComplete(this, _unsignedUpdateAvailable, success);
     }
 
 
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 46dcdf0c5f..7b733b26e8 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateHandler.java
@@ -12,6 +12,8 @@ import net.i2p.router.util.RFC822Date;
 import net.i2p.router.web.ConfigUpdateHandler;
 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.FileUtil;
 import net.i2p.util.I2PAppThread;
@@ -54,18 +56,7 @@ class UnsignedUpdateHandler implements Checker, Updater {
             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) {}
+        long ms = _context.getProperty(NewsHelper.PROP_LAST_UPDATE_TIME, 0L);
         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.
@@ -90,7 +81,7 @@ class UnsignedUpdateHandler implements Checker, Updater {
     @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())
+        if (type != ROUTER_UNSIGNED || method != HTTP || updateSources.isEmpty())
             return null;
         UpdateRunner update = new UnsignedUpdateRunner(_context, updateSources);
         update.start();
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 c77a5ec921..e9ac30ce8a 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateRunner.java
@@ -9,6 +9,7 @@ import net.i2p.router.RouterContext;
 import net.i2p.router.util.RFC822Date;
 import net.i2p.router.web.ConfigUpdateHandler;
 import net.i2p.update.*;
+import static net.i2p.update.UpdateType.*;
 import net.i2p.util.EepGet;
 import net.i2p.util.FileUtil;
 import net.i2p.util.I2PAppThread;
@@ -31,7 +32,7 @@ class UnsignedUpdateRunner extends UpdateRunner {
 
 
     @Override
-    public UpdateType getType() { return UpdateType.ROUTER_UNSIGNED; }
+    public UpdateType getType() { return ROUTER_UNSIGNED; }
 
 
         /** Get the file */
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 a859cd92e7..192fdfbb7a 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UpdateRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UpdateRunner.java
@@ -83,6 +83,8 @@ class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusList
         _isRunning = true;
         try {
             update();
+        } catch (Throwable t) {
+            _mgr.notifyTaskFailed(this, "", t);
         } finally {
             _isRunning = false;
         }
@@ -207,6 +209,7 @@ class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusList
         _log.error("Update from " + url + " did not download completely (" +
                            bytesRemaining + " remaining after " + currentAttempt + " tries)");
         updateStatus("<b>" + _("Transfer failed from {0}", linkify(url)) + "</b>");
+        _mgr.notifyTaskFailed(this, "", null);
     }
 
     public void headerReceived(String url, int attemptNum, String key, String val) {}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
index 0340dde3e7..672303e128 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
@@ -101,10 +101,14 @@ public class ConfigUpdateHandler extends FormHandler {
                 addFormError("Update manager not registered, cannot check");
                 return;
             }
-            boolean a1 = mgr.checkAvailable(NEWS, 60*1000) != null;
+            if (mgr.isUpdateInProgress() || mgr.isCheckInProgress()) {
+                addFormError(_("Update or check already in progress"));
+                return;
+            }
+            boolean a1 = mgr.checkAvailable(NEWS, 30*1000) != null;
             boolean a2 = false;
             if ((!a1) && _updateUnsigned && _zipURL != null && _zipURL.length() > 0)
-                a2 = mgr.checkAvailable(ROUTER_UNSIGNED, 60*1000) != null;
+                a2 = mgr.checkAvailable(ROUTER_UNSIGNED, 30*1000) != null;
             if (a1 || a2) {
                 if ( (_updatePolicy == null) || (!_updatePolicy.equals("notify")) )
                     addFormNotice(_("Update available, attempting to download now"));
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 5510de3ddd..2c4cc16b25 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java
@@ -20,15 +20,15 @@ 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 */
+    /** @since 0.9.4 */
     public static final String PROP_LAST_CHECKED = "routerconsole.newsLastChecked";
-    /** @since 0.9.2 */
+    /** @since 0.9.4 */
     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
+     *  @since 0.9.4 was stored in system properties
      */
     public static boolean isAnyUpdateInProgress() {
         ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
@@ -39,7 +39,7 @@ public class NewsHelper extends ContentHelper {
     /**
      *  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
+     *  @since 0.9.4 was stored in system properties
      */
     public static boolean isUpdateInProgress() {
         ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
@@ -50,7 +50,7 @@ public class NewsHelper extends ContentHelper {
     }
 
     /**
-     *  @since 0.9.2 moved from NewsFetcher
+     *  @since 0.9.4 moved from NewsFetcher
      */
     public static boolean isUpdateAvailable() {
         ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
@@ -60,7 +60,7 @@ public class NewsHelper extends ContentHelper {
 
     /**
      *  @return null if none
-     *  @since 0.9.2 moved from NewsFetcher
+     *  @since 0.9.4 moved from NewsFetcher
      */
     public static String updateVersion() {
         ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
@@ -69,7 +69,7 @@ public class NewsHelper extends ContentHelper {
     }
 
     /**
-     *  @since 0.9.2 moved from NewsFetcher
+     *  @since 0.9.4 moved from NewsFetcher
      */
     public static boolean isUnsignedUpdateAvailable() {
         ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
@@ -79,7 +79,7 @@ public class NewsHelper extends ContentHelper {
 
     /**
      *  @return null if none
-     *  @since 0.9.2 moved from NewsFetcher
+     *  @since 0.9.4 moved from NewsFetcher
      */
     public static String unsignedUpdateVersion() {
         ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
@@ -97,7 +97,7 @@ public class NewsHelper extends ContentHelper {
 
     /**
      *  @return "" if none
-     *  @since 0.9.2 moved from UpdateHelper
+     *  @since 0.9.4 moved from UpdateHelper
      */
     public static String getUpdateStatus() {
         ConsoleUpdateManager mgr = ConsoleUpdateManager.getInstance();
@@ -122,19 +122,13 @@ public class NewsHelper extends ContentHelper {
     }
 
     /**
-     *  @since 0.9.2
+     *  @since 0.9.4
      */
     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) {}
+        long last = ctx.getProperty(PROP_LAST_HIDDEN, 0L);
         return lastUpdated > last;
     }
 
@@ -148,18 +142,16 @@ public class NewsHelper extends ContentHelper {
 
     /**
      *  Save config with the timestamp of the current news to hide, or 0 to show
-     *  @since 0.9.2
+     *  @since 0.9.4
      */
     public static void showNews(RouterContext ctx, boolean yes) {
-         long lastUpdated = 0;
-/////// FIME from props, or from last mod time?
-        long stamp = yes ? 0 : lastUpdated;
+        long stamp = yes ? 0 : lastUpdated(ctx);
         ctx.router().saveConfig(PROP_LAST_HIDDEN, Long.toString(stamp));
     }
 
     /**
      *  @return HTML
-     *  @since 0.9.2 moved from NewsFetcher
+     *  @since 0.9.4 moved from NewsFetcher
      */
     public String status() {
         return status(_context);
@@ -167,7 +159,7 @@ public class NewsHelper extends ContentHelper {
 
     /**
      *  @return HTML
-     *  @since 0.9.2 moved from NewsFetcher
+     *  @since 0.9.4 moved from NewsFetcher
      */
     public static String status(RouterContext ctx) {
          StringBuilder buf = new StringBuilder(128);
@@ -202,7 +194,7 @@ public class NewsHelper extends ContentHelper {
     }
     
     /**
-     *  @since 0.9.2 moved from NewsFetcher
+     *  @since 0.9.4 moved from NewsFetcher
      */
     public static boolean dontInstall(RouterContext ctx) {
         File test = new File(ctx.getBaseDir(), "history.txt");
@@ -212,31 +204,22 @@ public class NewsHelper extends ContentHelper {
     }
 
     /**
-     *  @since 0.9.2
+     *  @since 0.9.4
      */
     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;
+        return ctx.getProperty(PROP_LAST_CHECKED, 0L);
     }
 
     /**
      *  When the news was last downloaded
-     *  @since 0.9.2
+     *  @since 0.9.4
      */
     public static long lastUpdated(RouterContext ctx) {
-        String lc = ctx.getProperty(PROP_LAST_UPDATED);
-        if (lc == null) {
-            try {
-                return Long.parseLong(lc);
-            } catch (NumberFormatException nfe) {}
-        }
+        long rv = ctx.getProperty(PROP_LAST_UPDATED, 0L);
+        if (rv > 0)
+            return rv;
         File newsFile = new File(ctx.getRouterDir(), NEWS_FILE);
-        long rv = newsFile.lastModified();
+        rv = newsFile.lastModified();
         ctx.router().saveConfig(PROP_LAST_UPDATED, Long.toString(rv));
         return rv;
     }
diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java
index 05475eea00..6b542138be 100644
--- a/core/java/src/net/i2p/I2PAppContext.java
+++ b/core/java/src/net/i2p/I2PAppContext.java
@@ -479,6 +479,26 @@ public class I2PAppContext {
         return ival;
     }
 
+    /**
+     * Return a long with a long default
+     * @since 0.9.4
+     */
+    public long getProperty(String propName, long defaultVal) {
+        String val = null;
+        if (_overrideProps != null) {
+            val = _overrideProps.getProperty(propName);
+            if (val == null)
+                val = System.getProperty(propName);
+        }
+        long rv = defaultVal;
+        if (val != null) {
+            try {
+                rv = Long.parseLong(val);
+            } catch (NumberFormatException nfe) {}
+        }
+        return rv;
+    }
+
     /**
      * Return a boolean with a boolean default
      * @since 0.7.12
diff --git a/core/java/src/net/i2p/update/UpdateMethod.java b/core/java/src/net/i2p/update/UpdateMethod.java
index 3f1103ac3f..a5aee879ff 100644
--- a/core/java/src/net/i2p/update/UpdateMethod.java
+++ b/core/java/src/net/i2p/update/UpdateMethod.java
@@ -9,6 +9,7 @@ public enum UpdateMethod {
     METHOD_DUMMY,
     HTTP,              // .i2p or via outproxy
     HTTP_CLEARNET,     // direct non-.i2p
+    HTTPS_CLEARNET,    // direct non-.i2p
     TORRENT,
     GNUTELLA, IMULE, TAHOE_LAFS,
     DEBIAN
diff --git a/core/java/src/net/i2p/update/UpdateType.java b/core/java/src/net/i2p/update/UpdateType.java
index 66fd623cf6..d4a7020725 100644
--- a/core/java/src/net/i2p/update/UpdateType.java
+++ b/core/java/src/net/i2p/update/UpdateType.java
@@ -12,5 +12,6 @@ public enum UpdateType {
     ROUTER_SIGNED_PACK200,      // unused, use ROUTER_SIGNED for both
     ROUTER_UNSIGNED,
     PLUGIN, PLUGIN_INSTALL,
-    GEOIP, BLOCKLIST, RESEED
+    GEOIP, BLOCKLIST, RESEED,
+    HOMEPAGE
 }
-- 
GitLab