From c0b9fe034021344e0f787a90a50aabdf87d685c6 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Tue, 29 Nov 2011 15:25:40 +0000
Subject: [PATCH]   * Router: Refactor periodic tasks to their own files

---
 apps/routerconsole/java/bundle-messages.sh    |   2 +-
 router/java/src/net/i2p/router/Router.java    | 248 +-----------------
 .../i2p/router/tasks/CoalesceStatsEvent.java  | 107 ++++++++
 .../net/i2p/router/tasks/MarkLiveliness.java  |  55 ++++
 .../router/tasks/PersistRouterInfoJob.java    |  59 +++++
 .../src/net/i2p/router/tasks/Republish.java   |  38 +++
 .../router/{ => tasks}/RouterWatchdog.java    |   7 +-
 .../net/i2p/router/tasks/ShutdownHook.java    |  37 +++
 .../src/net/i2p/router/tasks/Spinner.java     |  24 ++
 .../tasks/UpdateRoutingKeyModifierJob.java    |  61 +++++
 .../src/net/i2p/router/tasks/package.html     |   9 +
 11 files changed, 403 insertions(+), 244 deletions(-)
 create mode 100644 router/java/src/net/i2p/router/tasks/CoalesceStatsEvent.java
 create mode 100644 router/java/src/net/i2p/router/tasks/MarkLiveliness.java
 create mode 100644 router/java/src/net/i2p/router/tasks/PersistRouterInfoJob.java
 create mode 100644 router/java/src/net/i2p/router/tasks/Republish.java
 rename router/java/src/net/i2p/router/{ => tasks}/RouterWatchdog.java (97%)
 create mode 100644 router/java/src/net/i2p/router/tasks/ShutdownHook.java
 create mode 100644 router/java/src/net/i2p/router/tasks/Spinner.java
 create mode 100644 router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java
 create mode 100644 router/java/src/net/i2p/router/tasks/package.html

diff --git a/apps/routerconsole/java/bundle-messages.sh b/apps/routerconsole/java/bundle-messages.sh
index e75e0d8976..dd3ea83224 100755
--- a/apps/routerconsole/java/bundle-messages.sh
+++ b/apps/routerconsole/java/bundle-messages.sh
@@ -44,7 +44,7 @@ fi
 # list specific files in core/ and router/ here, so we don't scan the whole tree
 ROUTERFILES="\
    ../../../core/java/src/net/i2p/data/DataHelper.java \
-   ../../../router/java/src/net/i2p/router/Router.java \
+   ../../../router/java/src/net/i2p/router/tasks/CoalesceStatsEvent.java \
    ../../../router/java/src/net/i2p/router/RouterThrottleImpl.java \
    ../../../router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java \
    ../../../router/java/src/net/i2p/router/transport/TransportManager.java \
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index 77994bdb3a..b2ca9c5e9b 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -15,11 +15,8 @@ import java.io.FileOutputStream;
 import java.io.InputStreamReader;
 import java.io.IOException;
 import java.io.Writer;
-import java.text.DecimalFormat;
-import java.util.Calendar;
 import java.util.Collections;
 import java.util.Date;
-import java.util.GregorianCalendar;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -43,9 +40,9 @@ import net.i2p.router.message.GarlicMessageHandler;
 import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
 import net.i2p.router.startup.StartupJob;
 import net.i2p.router.startup.WorkingDir;
+import net.i2p.router.tasks.*;
 import net.i2p.router.transport.FIFOBandwidthLimiter;
 import net.i2p.router.transport.udp.UDPTransport;
-import net.i2p.stat.Rate;
 import net.i2p.stat.RateStat;
 import net.i2p.stat.StatManager;
 import net.i2p.util.ByteCache;
@@ -57,7 +54,6 @@ import net.i2p.util.Log;
 import net.i2p.util.SecureFileOutputStream;
 import net.i2p.util.SimpleByteCache;
 import net.i2p.util.SimpleScheduler;
-import net.i2p.util.SimpleTimer;
 
 /**
  * Main driver for the router.
@@ -94,7 +90,7 @@ public class Router implements RouterClock.ClockShiftListener {
     public static final int NETWORK_ID = 2;
     
     /** coalesce stats this often - should be a little less than one minute, so the graphs get updated */
-    private static final int COALESCE_TIME = 50*1000;
+    public static final int COALESCE_TIME = 50*1000;
 
     /** this puts an 'H' in your routerInfo **/
     public final static String PROP_HIDDEN = "router.hiddenMode";
@@ -310,7 +306,7 @@ public class Router implements RouterClock.ClockShiftListener {
     }
     
     /** @since 0.8.8 */
-    private static final void clearCaches() {
+    public static final void clearCaches() {
         ByteCache.clearAll();
         SimpleByteCache.clearAll();
     }
@@ -510,7 +506,7 @@ public class Router implements RouterClock.ClockShiftListener {
             setRouterInfo(ri);
             if (!ri.isValid())
                 throw new DataFormatException("Our RouterInfo has a bad signature");
-            Republish r = new Republish();
+            Republish r = new Republish(_context);
             if (blockingRebuild)
                 r.timeReached();
             else
@@ -519,18 +515,7 @@ public class Router implements RouterClock.ClockShiftListener {
             _log.log(Log.CRIT, "Internal error - unable to sign our own address?!", dfe);
         }
     }
-    
-    private class Republish implements SimpleTimer.TimedEvent {
-        public void timeReached() {
-            try {
-                _context.netDb().publish(getRouterInfo());
-            } catch (IllegalArgumentException iae) {
-                _log.log(Log.CRIT, "Local router info is invalid?  rebuilding a new identity", iae);
-                rebuildNewIdentity();
-            }
-        }
-    }
-    
+
     // publicize our ballpark capacity
     public static final char CAPABILITY_BW12 = 'K';
     public static final char CAPABILITY_BW32 = 'L';
@@ -1032,8 +1017,9 @@ public class Router implements RouterClock.ClockShiftListener {
 
     /**
      *  Cancel the JVM runtime hook before calling this.
+     *  NOT to be called by others, use shutdown().
      */
-    private void shutdown2(int exitCode) {
+    public void shutdown2(int exitCode) {
         // So we can get all the way to the end
         // No, you can't do Thread.currentThread.setDaemon(false)
         if (_killVMOnEnd) {
@@ -1739,224 +1725,4 @@ public class Router implements RouterClock.ClockShiftListener {
             recv = (int)rs.getRate(5*60*1000).getAverageValue();
         return Math.max(send, recv);
     }
-    
-    /**
-     *  Mark a string for extraction by xgettext and translation.
-     *  Use this only in static initializers.
-     *  It does not translate!
-     *  @return s
-     *  @since 0.8.7
-     */
-    private static final String _x(String s) {
-        return s;
-    }
-
-/* following classes are now private static inner classes, didn't bother to reindent */
-
-private static final long LOW_MEMORY_THRESHOLD = 5 * 1024 * 1024;
-
-/**
- * coalesce the stats framework every minute
- *
- */
-private static class CoalesceStatsEvent implements SimpleTimer.TimedEvent {
-    private RouterContext _ctx;
-    private long _maxMemory;
-
-    public CoalesceStatsEvent(RouterContext ctx) { 
-        _ctx = ctx; 
-        // NOTE TO TRANSLATORS - each of these phrases is a description for a statistic
-        // to be displayed on /stats.jsp and in the graphs on /graphs.jsp.
-        // Please keep relatively short so it will fit on the graphs.
-        ctx.statManager().createRequiredRateStat("bw.receiveBps", _x("Message receive rate (bytes/sec)"), "Bandwidth", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
-        ctx.statManager().createRequiredRateStat("bw.sendBps", _x("Message send rate (bytes/sec)"), "Bandwidth", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
-        ctx.statManager().createRequiredRateStat("bw.sendRate", _x("Low-level send rate (bytes/sec)"), "Bandwidth", new long[] { 60*1000l, 5*60*1000l, 10*60*1000l, 60*60*1000l });
-        ctx.statManager().createRequiredRateStat("bw.recvRate", _x("Low-level receive rate (bytes/sec)"), "Bandwidth", new long[] { 60*1000l, 5*60*1000l, 10*60*1000l, 60*60*1000l });
-        ctx.statManager().createRequiredRateStat("router.activePeers", _x("How many peers we are actively talking with"), "Throttle", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
-        ctx.statManager().createRateStat("router.activeSendPeers", "How many peers we've sent to this minute", "Throttle", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
-        ctx.statManager().createRateStat("router.highCapacityPeers", "How many high capacity peers we know", "Throttle", new long[] { 5*60*1000, 60*60*1000 });
-        ctx.statManager().createRequiredRateStat("router.fastPeers", _x("Known fast peers"), "Throttle", new long[] { 5*60*1000, 60*60*1000 });
-        _maxMemory = Runtime.getRuntime().maxMemory();
-        String legend = "(Bytes)";
-        if (_maxMemory < Long.MAX_VALUE)
-            legend += " Max is " + DataHelper.formatSize(_maxMemory) + 'B';
-        // router.memoryUsed currently has the max size in the description so it can't be tagged
-        ctx.statManager().createRequiredRateStat("router.memoryUsed", legend, "Router", new long[] { 60*1000 });
-    }
-    private RouterContext getContext() { return _ctx; }
-    public void timeReached() {
-        int active = getContext().commSystem().countActivePeers();
-        getContext().statManager().addRateData("router.activePeers", active, 60*1000);
-
-        int activeSend = getContext().commSystem().countActiveSendPeers();
-        getContext().statManager().addRateData("router.activeSendPeers", activeSend, 60*1000);
-
-        int fast = getContext().profileOrganizer().countFastPeers();
-        getContext().statManager().addRateData("router.fastPeers", fast, 60*1000);
-
-        int highCap = getContext().profileOrganizer().countHighCapacityPeers();
-        getContext().statManager().addRateData("router.highCapacityPeers", highCap, 60*1000);
-
-        getContext().statManager().addRateData("bw.sendRate", (long)getContext().bandwidthLimiter().getSendBps(), 0);
-        getContext().statManager().addRateData("bw.recvRate", (long)getContext().bandwidthLimiter().getReceiveBps(), 0);
-        
-        long used = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
-        getContext().statManager().addRateData("router.memoryUsed", used, 0);
-        if (_maxMemory - used < LOW_MEMORY_THRESHOLD)
-            clearCaches();
-
-        getContext().tunnelDispatcher().updateParticipatingStats(COALESCE_TIME);
-
-        getContext().statManager().coalesceStats();
-
-        RateStat receiveRate = getContext().statManager().getRate("transport.receiveMessageSize");
-        if (receiveRate != null) {
-            Rate rate = receiveRate.getRate(60*1000);
-            if (rate != null) { 
-                double bytes = rate.getLastTotalValue();
-                double bps = (bytes*1000.0d)/rate.getPeriod(); 
-                getContext().statManager().addRateData("bw.receiveBps", (long)bps, 60*1000);
-            }
-        }
-
-        RateStat sendRate = getContext().statManager().getRate("transport.sendMessageSize");
-        if (sendRate != null) {
-            Rate rate = sendRate.getRate(60*1000);
-            if (rate != null) {
-                double bytes = rate.getLastTotalValue();
-                double bps = (bytes*1000.0d)/rate.getPeriod(); 
-                getContext().statManager().addRateData("bw.sendBps", (long)bps, 60*1000);
-            }
-        }
-    }
-}
-
-/**
- * Update the routing Key modifier every day at midnight (plus on startup).
- * This is done here because we want to make sure the key is updated before anyone
- * uses it.
- */
-private static class UpdateRoutingKeyModifierJob extends JobImpl {
-    private Log _log;
-    private Calendar _cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
-    public UpdateRoutingKeyModifierJob(RouterContext ctx) { 
-        super(ctx);
-    }
-    public String getName() { return "Update Routing Key Modifier"; }
-    public void runJob() {
-        _log = getContext().logManager().getLog(getClass());
-        getContext().routingKeyGenerator().generateDateBasedModData();
-        requeue(getTimeTillMidnight());
-    }
-    private long getTimeTillMidnight() {
-        long now = getContext().clock().now();
-        _cal.setTime(new Date(now));
-        _cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR));               // gcj <= 4.0 workaround
-        _cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround
-        _cal.add(Calendar.DATE, 1);
-        _cal.set(Calendar.HOUR_OF_DAY, 0);
-        _cal.set(Calendar.MINUTE, 0);
-        _cal.set(Calendar.SECOND, 0);
-        _cal.set(Calendar.MILLISECOND, 0);
-        long then = _cal.getTime().getTime();
-        long howLong = then - now;
-        if (howLong < 0) // hi kaffe
-            howLong = 24*60*60*1000l + howLong;
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Time till midnight: " + howLong + "ms");
-        return howLong;
-    }
-}
-
-/**
- *  Write a timestamp to the ping file where
- *  other routers trying to use the same configuration can see it
- */
-private static class MarkLiveliness implements SimpleTimer.TimedEvent {
-    private final Router _router;
-    private final File _pingFile;
-
-    public MarkLiveliness(Router router, File pingFile) {
-        _router = router;
-        _pingFile = pingFile;
-        _pingFile.deleteOnExit();
-    }
-
-    public void timeReached() {
-        if (_router.isAlive())
-            ping();
-        else
-            _pingFile.delete();
-    }
-
-    private void ping() {
-        FileOutputStream fos = null;
-        try { 
-            fos = new SecureFileOutputStream(_pingFile);
-            fos.write(("" + System.currentTimeMillis()).getBytes());
-        } catch (IOException ioe) {
-            System.err.println("Error writing to ping file");
-            ioe.printStackTrace();
-        } finally {
-            if (fos != null) try { fos.close(); } catch (IOException ioe) {}
-        }
-    }
-}
-
-/**
- *  Just for failsafe. Standard shutdown should cancel this.
- */
-private static class ShutdownHook extends Thread {
-    private final RouterContext _context;
-    private static int __id = 0;
-    private final int _id;
-
-    public ShutdownHook(RouterContext ctx) {
-        _context = ctx;
-        _id = ++__id;
-    }
-
-    @Override
-    public void run() {
-        setName("Router " + _id + " shutdown");
-        Log l = _context.logManager().getLog(Router.class);
-        l.log(Log.CRIT, "Shutting down the router...");
-        _context.router().shutdown2(Router.EXIT_HARD);
-    }
-}
-
-/** update the router.info file whenever its, er, updated */
-private static class PersistRouterInfoJob extends JobImpl {
-    public PersistRouterInfoJob(RouterContext ctx) { 
-        super(ctx); 
-    }
-
-    public String getName() { return "Persist Updated Router Information"; }
-
-    public void runJob() {
-        Log _log = getContext().logManager().getLog(PersistRouterInfoJob.class);
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Persisting updated router info");
-
-        String infoFilename = getContext().getProperty(PROP_INFO_FILENAME, PROP_INFO_FILENAME_DEFAULT);
-        File infoFile = new File(getContext().getRouterDir(), infoFilename);
-
-        RouterInfo info = getContext().router().getRouterInfo();
-
-        FileOutputStream fos = null;
-        synchronized (getContext().router().routerInfoFileLock) {
-            try {
-                fos = new SecureFileOutputStream(infoFile);
-                info.writeBytes(fos);
-            } catch (DataFormatException dfe) {
-                _log.error("Error rebuilding the router information", dfe);
-            } catch (IOException ioe) {
-                _log.error("Error writing out the rebuilt router information", ioe);
-            } finally {
-                if (fos != null) try { fos.close(); } catch (IOException ioe) {}
-            }
-        }
-    }
-}
-
 }
diff --git a/router/java/src/net/i2p/router/tasks/CoalesceStatsEvent.java b/router/java/src/net/i2p/router/tasks/CoalesceStatsEvent.java
new file mode 100644
index 0000000000..bb6613f1fa
--- /dev/null
+++ b/router/java/src/net/i2p/router/tasks/CoalesceStatsEvent.java
@@ -0,0 +1,107 @@
+package net.i2p.router.tasks;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2003 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import net.i2p.data.DataHelper;
+import net.i2p.router.Router;
+import net.i2p.router.RouterContext;
+import net.i2p.stat.Rate;
+import net.i2p.stat.RateStat;
+import net.i2p.util.SimpleTimer;
+
+/**
+ * Coalesce the stats framework every minute
+ *
+ * @since 0.8.12 moved from Router.java
+ */
+public class CoalesceStatsEvent implements SimpleTimer.TimedEvent {
+    private final RouterContext _ctx;
+    private final long _maxMemory;
+    private static final long LOW_MEMORY_THRESHOLD = 5 * 1024 * 1024;
+
+    public CoalesceStatsEvent(RouterContext ctx) { 
+        _ctx = ctx; 
+        // NOTE TO TRANSLATORS - each of these phrases is a description for a statistic
+        // to be displayed on /stats.jsp and in the graphs on /graphs.jsp.
+        // Please keep relatively short so it will fit on the graphs.
+        ctx.statManager().createRequiredRateStat("bw.receiveBps", _x("Message receive rate (bytes/sec)"), "Bandwidth", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
+        ctx.statManager().createRequiredRateStat("bw.sendBps", _x("Message send rate (bytes/sec)"), "Bandwidth", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
+        ctx.statManager().createRequiredRateStat("bw.sendRate", _x("Low-level send rate (bytes/sec)"), "Bandwidth", new long[] { 60*1000l, 5*60*1000l, 10*60*1000l, 60*60*1000l });
+        ctx.statManager().createRequiredRateStat("bw.recvRate", _x("Low-level receive rate (bytes/sec)"), "Bandwidth", new long[] { 60*1000l, 5*60*1000l, 10*60*1000l, 60*60*1000l });
+        ctx.statManager().createRequiredRateStat("router.activePeers", _x("How many peers we are actively talking with"), "Throttle", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
+        ctx.statManager().createRateStat("router.activeSendPeers", "How many peers we've sent to this minute", "Throttle", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
+        ctx.statManager().createRateStat("router.highCapacityPeers", "How many high capacity peers we know", "Throttle", new long[] { 5*60*1000, 60*60*1000 });
+        ctx.statManager().createRequiredRateStat("router.fastPeers", _x("Known fast peers"), "Throttle", new long[] { 5*60*1000, 60*60*1000 });
+        _maxMemory = Runtime.getRuntime().maxMemory();
+        String legend = "(Bytes)";
+        if (_maxMemory < Long.MAX_VALUE)
+            legend += " Max is " + DataHelper.formatSize(_maxMemory) + 'B';
+        // router.memoryUsed currently has the max size in the description so it can't be tagged
+        ctx.statManager().createRequiredRateStat("router.memoryUsed", legend, "Router", new long[] { 60*1000 });
+    }
+
+    private RouterContext getContext() { return _ctx; }
+
+    public void timeReached() {
+        int active = getContext().commSystem().countActivePeers();
+        getContext().statManager().addRateData("router.activePeers", active, 60*1000);
+
+        int activeSend = getContext().commSystem().countActiveSendPeers();
+        getContext().statManager().addRateData("router.activeSendPeers", activeSend, 60*1000);
+
+        int fast = getContext().profileOrganizer().countFastPeers();
+        getContext().statManager().addRateData("router.fastPeers", fast, 60*1000);
+
+        int highCap = getContext().profileOrganizer().countHighCapacityPeers();
+        getContext().statManager().addRateData("router.highCapacityPeers", highCap, 60*1000);
+
+        getContext().statManager().addRateData("bw.sendRate", (long)getContext().bandwidthLimiter().getSendBps());
+        getContext().statManager().addRateData("bw.recvRate", (long)getContext().bandwidthLimiter().getReceiveBps());
+        
+        long used = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+        getContext().statManager().addRateData("router.memoryUsed", used);
+        if (_maxMemory - used < LOW_MEMORY_THRESHOLD)
+            Router.clearCaches();
+
+        getContext().tunnelDispatcher().updateParticipatingStats(Router.COALESCE_TIME);
+
+        getContext().statManager().coalesceStats();
+
+        RateStat receiveRate = getContext().statManager().getRate("transport.receiveMessageSize");
+        if (receiveRate != null) {
+            Rate rate = receiveRate.getRate(60*1000);
+            if (rate != null) { 
+                double bytes = rate.getLastTotalValue();
+                double bps = (bytes*1000.0d)/rate.getPeriod(); 
+                getContext().statManager().addRateData("bw.receiveBps", (long)bps, 60*1000);
+            }
+        }
+
+        RateStat sendRate = getContext().statManager().getRate("transport.sendMessageSize");
+        if (sendRate != null) {
+            Rate rate = sendRate.getRate(60*1000);
+            if (rate != null) {
+                double bytes = rate.getLastTotalValue();
+                double bps = (bytes*1000.0d)/rate.getPeriod(); 
+                getContext().statManager().addRateData("bw.sendBps", (long)bps, 60*1000);
+            }
+        }
+    }
+    
+    /**
+     *  Mark a string for extraction by xgettext and translation.
+     *  Use this only in static initializers.
+     *  It does not translate!
+     *  @return s
+     *  @since 0.8.7
+     */
+    private static final String _x(String s) {
+        return s;
+    }
+}
diff --git a/router/java/src/net/i2p/router/tasks/MarkLiveliness.java b/router/java/src/net/i2p/router/tasks/MarkLiveliness.java
new file mode 100644
index 0000000000..9c9f4f1291
--- /dev/null
+++ b/router/java/src/net/i2p/router/tasks/MarkLiveliness.java
@@ -0,0 +1,55 @@
+package net.i2p.router.tasks;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2003 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import net.i2p.router.Router;
+import net.i2p.util.SecureFileOutputStream;
+import net.i2p.util.SimpleTimer;
+
+/**
+ *  Write a timestamp to the ping file where
+ *  other routers trying to use the same configuration can see it
+ *
+ * @since 0.8.12 moved from Router.java
+ */
+public class MarkLiveliness implements SimpleTimer.TimedEvent {
+    private final Router _router;
+    private final File _pingFile;
+
+    public MarkLiveliness(Router router, File pingFile) {
+        _router = router;
+        _pingFile = pingFile;
+        _pingFile.deleteOnExit();
+    }
+
+    public void timeReached() {
+        if (_router.isAlive())
+            ping();
+        else
+            _pingFile.delete();
+    }
+
+    private void ping() {
+        FileOutputStream fos = null;
+        try { 
+            fos = new SecureFileOutputStream(_pingFile);
+            fos.write(("" + System.currentTimeMillis()).getBytes());
+        } catch (IOException ioe) {
+            System.err.println("Error writing to ping file");
+            ioe.printStackTrace();
+        } finally {
+            if (fos != null) try { fos.close(); } catch (IOException ioe) {}
+        }
+    }
+}
+
diff --git a/router/java/src/net/i2p/router/tasks/PersistRouterInfoJob.java b/router/java/src/net/i2p/router/tasks/PersistRouterInfoJob.java
new file mode 100644
index 0000000000..c29dd25772
--- /dev/null
+++ b/router/java/src/net/i2p/router/tasks/PersistRouterInfoJob.java
@@ -0,0 +1,59 @@
+package net.i2p.router.tasks;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2003 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import net.i2p.data.DataFormatException;
+import net.i2p.data.RouterInfo;
+import net.i2p.router.JobImpl;
+import net.i2p.router.Router;
+import net.i2p.router.RouterContext;
+import net.i2p.util.Log;
+import net.i2p.util.SecureFileOutputStream;
+
+/**
+ *  Update the router.info file whenever its, er, updated
+ *
+ *  @since 0.8.12 moved from Router.java
+ */
+public class PersistRouterInfoJob extends JobImpl {
+    public PersistRouterInfoJob(RouterContext ctx) { 
+        super(ctx); 
+    }
+
+    public String getName() { return "Persist Updated Router Information"; }
+
+    public void runJob() {
+        Log _log = getContext().logManager().getLog(PersistRouterInfoJob.class);
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Persisting updated router info");
+
+        String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT);
+        File infoFile = new File(getContext().getRouterDir(), infoFilename);
+
+        RouterInfo info = getContext().router().getRouterInfo();
+
+        FileOutputStream fos = null;
+        synchronized (getContext().router().routerInfoFileLock) {
+            try {
+                fos = new SecureFileOutputStream(infoFile);
+                info.writeBytes(fos);
+            } catch (DataFormatException dfe) {
+                _log.error("Error rebuilding the router information", dfe);
+            } catch (IOException ioe) {
+                _log.error("Error writing out the rebuilt router information", ioe);
+            } finally {
+                if (fos != null) try { fos.close(); } catch (IOException ioe) {}
+            }
+        }
+    }
+}
diff --git a/router/java/src/net/i2p/router/tasks/Republish.java b/router/java/src/net/i2p/router/tasks/Republish.java
new file mode 100644
index 0000000000..111456b896
--- /dev/null
+++ b/router/java/src/net/i2p/router/tasks/Republish.java
@@ -0,0 +1,38 @@
+package net.i2p.router.tasks;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2003 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import net.i2p.router.Router;
+import net.i2p.router.RouterContext;
+import net.i2p.util.SimpleTimer;
+import net.i2p.util.Log;
+
+/**
+ * Periodically publish our RouterInfo to the netdb
+ *
+ * @since 0.8.12 moved from Router.java
+ */
+public class Republish implements SimpleTimer.TimedEvent {
+    private final RouterContext _context;
+
+    public Republish(RouterContext ctx) {
+        _context = ctx;
+    }
+
+    public void timeReached() {
+        try {
+            _context.netDb().publish(_context.router().getRouterInfo());
+        } catch (IllegalArgumentException iae) {
+            Log log = _context.logManager().getLog(Router.class);
+            log.log(Log.CRIT, "Local router info is invalid?  rebuilding a new identity", iae);
+            _context.router().rebuildNewIdentity();
+        }
+    }
+}
+
diff --git a/router/java/src/net/i2p/router/RouterWatchdog.java b/router/java/src/net/i2p/router/tasks/RouterWatchdog.java
similarity index 97%
rename from router/java/src/net/i2p/router/RouterWatchdog.java
rename to router/java/src/net/i2p/router/tasks/RouterWatchdog.java
index 80d7d9bcc9..7cc029b77a 100644
--- a/router/java/src/net/i2p/router/RouterWatchdog.java
+++ b/router/java/src/net/i2p/router/tasks/RouterWatchdog.java
@@ -1,8 +1,11 @@
-package net.i2p.router;
+package net.i2p.router.tasks;
 
 import java.io.File;
 
 import net.i2p.data.DataHelper;
+import net.i2p.router.Job;
+import net.i2p.router.Router;
+import net.i2p.router.RouterContext;
 import net.i2p.stat.Rate;
 import net.i2p.stat.RateStat;
 import net.i2p.util.ShellCommand;
@@ -13,7 +16,7 @@ import net.i2p.util.Log;
  * they have, restart the JVM)
  *
  */
-class RouterWatchdog implements Runnable {
+public class RouterWatchdog implements Runnable {
     private final Log _log;
     private final RouterContext _context;
     private int _consecutiveErrors;
diff --git a/router/java/src/net/i2p/router/tasks/ShutdownHook.java b/router/java/src/net/i2p/router/tasks/ShutdownHook.java
new file mode 100644
index 0000000000..9bc57d6435
--- /dev/null
+++ b/router/java/src/net/i2p/router/tasks/ShutdownHook.java
@@ -0,0 +1,37 @@
+package net.i2p.router.tasks;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2003 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import net.i2p.router.Router;
+import net.i2p.router.RouterContext;
+import net.i2p.util.Log;
+
+/**
+ *  Just for failsafe. Standard shutdown should cancel this.
+ *
+ *  @since 0.8.12 moved from Router.java
+ */
+public class ShutdownHook extends Thread {
+    private final RouterContext _context;
+    private static int __id = 0;
+    private final int _id;
+
+    public ShutdownHook(RouterContext ctx) {
+        _context = ctx;
+        _id = ++__id;
+    }
+
+    @Override
+    public void run() {
+        setName("Router " + _id + " shutdown");
+        Log l = _context.logManager().getLog(Router.class);
+        l.log(Log.CRIT, "Shutting down the router...");
+        _context.router().shutdown2(Router.EXIT_HARD);
+    }
+}
diff --git a/router/java/src/net/i2p/router/tasks/Spinner.java b/router/java/src/net/i2p/router/tasks/Spinner.java
new file mode 100644
index 0000000000..99c4f9ef30
--- /dev/null
+++ b/router/java/src/net/i2p/router/tasks/Spinner.java
@@ -0,0 +1,24 @@
+package net.i2p.router.tasks;
+
+/**
+ *  A non-daemon thread to let
+ *  the shutdown task get all the way to the end
+ *
+ *  @since 0.8.12 moved from Router.java
+ */
+public class Spinner extends Thread {
+
+    public Spinner() {
+        super();
+        setName("Shutdown Spinner");
+        setDaemon(false);
+    }
+
+    @Override
+    public void run() {
+        try {
+            sleep(60*1000);
+        } catch (InterruptedException ie) {}
+    }
+}
+
diff --git a/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java b/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java
new file mode 100644
index 0000000000..600292e025
--- /dev/null
+++ b/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java
@@ -0,0 +1,61 @@
+package net.i2p.router.tasks;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2003 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import net.i2p.router.JobImpl;
+import net.i2p.router.RouterContext;
+import net.i2p.util.Log;
+
+/**
+ * Update the routing Key modifier every day at midnight (plus on startup).
+ * This is done here because we want to make sure the key is updated before anyone
+ * uses it.
+ *
+ * @since 0.8.12 moved from Router.java
+ */
+public class UpdateRoutingKeyModifierJob extends JobImpl {
+    private Log _log;
+    private Calendar _cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
+
+    public UpdateRoutingKeyModifierJob(RouterContext ctx) { 
+        super(ctx);
+    }
+
+    public String getName() { return "Update Routing Key Modifier"; }
+
+    public void runJob() {
+        _log = getContext().logManager().getLog(getClass());
+        getContext().routingKeyGenerator().generateDateBasedModData();
+        requeue(getTimeTillMidnight());
+    }
+
+    private long getTimeTillMidnight() {
+        long now = getContext().clock().now();
+        _cal.setTime(new Date(now));
+        _cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR));               // gcj <= 4.0 workaround
+        _cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround
+        _cal.add(Calendar.DATE, 1);
+        _cal.set(Calendar.HOUR_OF_DAY, 0);
+        _cal.set(Calendar.MINUTE, 0);
+        _cal.set(Calendar.SECOND, 0);
+        _cal.set(Calendar.MILLISECOND, 0);
+        long then = _cal.getTime().getTime();
+        long howLong = then - now;
+        if (howLong < 0) // hi kaffe
+            howLong = 24*60*60*1000l + howLong;
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Time till midnight: " + howLong + "ms");
+        return howLong;
+    }
+}
diff --git a/router/java/src/net/i2p/router/tasks/package.html b/router/java/src/net/i2p/router/tasks/package.html
new file mode 100644
index 0000000000..e032edd6cb
--- /dev/null
+++ b/router/java/src/net/i2p/router/tasks/package.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+<p>
+Miscellaneous classes, mostly things that are executed periodically as
+Jobs, Threads, and SimpleTimer.TimedEvents.
+These are used only by Router.java.
+</p>
+</body>
+</html>
-- 
GitLab