diff --git a/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java b/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java
index 34b550df67140765d9ea7ab0fd1580f1b592e1ac..077acdb7f02bbf7f1a8c13cd912cac599d994078 100644
--- a/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java
+++ b/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java
@@ -39,6 +39,7 @@ import net.i2p.I2PAppContext;
 public class Daemon {
     public static final String VERSION = "2.0.3";
     private static final Daemon _instance = new Daemon();
+    private boolean _running;
     
     /**
      * Update the router and published address books using remote data from the
@@ -126,6 +127,7 @@ public class Daemon {
     }
     
     public void run(String[] args) {
+        _running = true;
         String settingsLocation = "config.txt";
         File homeFile;
         if (args.length > 0) {
@@ -166,7 +168,7 @@ public class Daemon {
 	    // Static method, and redundent Thread.currentThread().sleep(5*60*1000);
         } catch (InterruptedException ie) {}
         
-        while (true) {
+        while (_running) {
             long delay = Long.parseLong((String) settings.get("update_delay"));
             if (delay < 1) {
                 delay = 1;
@@ -179,6 +181,8 @@ public class Daemon {
                 }
             } catch (InterruptedException exp) {
             }
+            if (!_running)
+                break;
             settings = ConfigParser.parse(settingsFile, defaultSettings);
         }
     }
@@ -192,4 +196,9 @@ public class Daemon {
             _instance.notifyAll();
         }
     }
+
+    public static void stop() {
+        _instance._running = false;
+        wakeup();
+    }
 }
diff --git a/apps/addressbook/java/src/net/i2p/addressbook/DaemonThread.java b/apps/addressbook/java/src/net/i2p/addressbook/DaemonThread.java
index b5afaaa2f5d6b680032e2c5c118ef0f73f4dbb9a..7c9e65994fc513571253e6d947b35aa7c264c434 100644
--- a/apps/addressbook/java/src/net/i2p/addressbook/DaemonThread.java
+++ b/apps/addressbook/java/src/net/i2p/addressbook/DaemonThread.java
@@ -51,4 +51,9 @@ public class DaemonThread extends Thread {
         //}
         Daemon.main(this.args);
     }
-}
\ No newline at end of file
+
+    public void halt() {
+        Daemon.stop();
+        interrupt();
+    }
+}
diff --git a/apps/addressbook/java/src/net/i2p/addressbook/Servlet.java b/apps/addressbook/java/src/net/i2p/addressbook/Servlet.java
index fceecfe5397be64ca01ec05470c1548c1d9de9ce..beb225ca0d6d60fcea8b69585b24abcbf91240d8 100644
--- a/apps/addressbook/java/src/net/i2p/addressbook/Servlet.java
+++ b/apps/addressbook/java/src/net/i2p/addressbook/Servlet.java
@@ -41,7 +41,7 @@ import javax.servlet.http.HttpServletResponse;
  *
  */
 public class Servlet extends HttpServlet {
-    private Thread thread;
+    private DaemonThread thread;
     private String nonce;
     private static final String PROP_NONCE = "addressbook.nonce";
 
@@ -88,4 +88,9 @@ public class Servlet extends HttpServlet {
         //System.out.println("INFO: config root under " + args[0]);
     }
 
+    @Override
+    public void destroy() {
+        this.thread.halt();
+        super.destroy();
+    }
 }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
index c909a112f011aa4de23efb4014c80eba3b89803a..d0d9a3bd8476d59c95c291ca3ddcc66016ab7683 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
@@ -40,6 +40,8 @@ public class SnarkManager implements Snark.CompleteListener {
     private I2PSnarkUtil _util;
     private PeerCoordinatorSet _peerCoordinatorSet;
     private ConnectionAcceptor _connectionAcceptor;
+    private Thread _monitor;
+    private boolean _running;
     
     public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
     public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
@@ -78,15 +80,22 @@ public class SnarkManager implements Snark.CompleteListener {
      *  for i2cp host/port or i2psnark.dir
      */
     public void start() {
+        _running = true;
         _peerCoordinatorSet = new PeerCoordinatorSet();
         _connectionAcceptor = new ConnectionAcceptor(_util);
         int minutes = getStartupDelayMinutes();
         _messages.add(_("Adding torrents in {0} minutes", minutes));
-        I2PAppThread monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor");
-        monitor.setDaemon(true);
-        monitor.start();
+        _monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true);
+        _monitor.start();
         _context.addShutdownTask(new SnarkManagerShutdown());
     }
+
+    public void stop() {
+        _running = false;
+        _monitor.interrupt();
+        _connectionAcceptor.halt();
+        (new SnarkManagerShutdown()).run();
+    }
     
     /** hook to I2PSnarkUtil for the servlet */
     public I2PSnarkUtil util() { return _util; }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
index c6f22a63df43230abb7f3bbc6496405067911aa2..cb72f2c1e06936f2472b9b17a5499a992aa5ea80 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
@@ -150,7 +150,7 @@ public class TrackerClient extends I2PAppThread
                 continue;
              String dest = _util.lookup(url.substring(7, slash));
              if (dest == null) {
-                _log.error("Announce host unknown: [" + url + "]");
+                _log.error("Announce host unknown: [" + url.substring(7, slash) + "]");
                 continue;
              }
              if (primary.startsWith("http://" + dest))
@@ -258,7 +258,7 @@ public class TrackerClient extends I2PAppThread
                     tr.started = true;
 
                     Set peers = info.getPeers();
-                    tr.seenPeers = peers.size();
+                    tr.seenPeers = info.getPeerCount();
                     if (coordinator.trackerSeenPeers < tr.seenPeers) // update rising number quickly
                         coordinator.trackerSeenPeers = tr.seenPeers;
                     if ( (left > 0) && (!completed) ) {
@@ -269,6 +269,7 @@ public class TrackerClient extends I2PAppThread
                         Iterator it = ordered.iterator();
                         while (it.hasNext()) {
                           Peer cur = (Peer)it.next();
+                          // FIXME if id == us || dest == us continue;
                           // only delay if we actually make an attempt to add peer
                           if(coordinator.addPeer(cur)) {
                             int delay = DELAY_MUL;
@@ -356,6 +357,10 @@ public class TrackerClient extends I2PAppThread
       + "&downloaded=" + downloaded
       + "&left=" + left
       + ((! event.equals(NO_EVENT)) ? ("&event=" + event) : "");
+    if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
+        s += "&numwant=0";
+    else
+        s += "&numwant=" + _util.getMaxConnections();
     _util.debug("Sending TrackerClient request: " + s, Snark.INFO);
       
     tr.lastRequestTime = System.currentTimeMillis();
@@ -430,7 +435,7 @@ public class TrackerClient extends I2PAppThread
            url.getPort() < 0;
   }
 
-  private class Tracker
+  private static class Tracker
   {
       String announce;
       boolean isPrimary;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
index c2a287270d290d8afc11257b897b2a7bbedad95b..84198f12f0f0eacca955c53cf14a018c91202f1e 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
@@ -23,6 +23,7 @@ package org.klomp.snark;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashSet;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -37,6 +38,8 @@ public class TrackerInfo
   private final String failure_reason;
   private final int interval;
   private final Set peers;
+  private int complete;
+  private int incomplete;
 
   public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo)
     throws IOException
@@ -68,11 +71,26 @@ public class TrackerInfo
           throw new InvalidBEncodingException("No interval given");
         else
           interval = beInterval.getInt();
+
         BEValue bePeers = (BEValue)m.get("peers");
         if (bePeers == null)
-          throw new InvalidBEncodingException("No peer list");
+          peers = Collections.EMPTY_SET;
         else
           peers = getPeers(bePeers.getList(), my_id, metainfo);
+
+        BEValue bev = (BEValue)m.get("complete");
+        if (bev != null) try {
+          complete = bev.getInt();
+          if (complete < 0)
+              complete = 0;
+        } catch (InvalidBEncodingException ibe) {}
+
+        bev = (BEValue)m.get("incomplete");
+        if (bev != null) try {
+          incomplete = bev.getInt();
+          if (incomplete < 0)
+              incomplete = 0;
+        } catch (InvalidBEncodingException ibe) {}
       }
   }
 
@@ -115,6 +133,12 @@ public class TrackerInfo
     return peers;
   }
 
+  public int getPeerCount()
+  {
+    int pc = peers == null ? 0 : peers.size();
+    return Math.max(pc, complete + incomplete - 1);
+  }
+
   public String getFailureReason()
   {
     return failure_reason;
@@ -132,6 +156,8 @@ public class TrackerInfo
       return "TrackerInfo[FAILED: " + failure_reason + "]";
     else
       return "TrackerInfo[interval=" + interval
+        + (complete > 0 ? (", complete=" + complete) : "" )
+        + (incomplete > 0 ? (", incomplete=" + incomplete) : "" )
         + ", peers=" + peers + "]";
   }
 }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
index 013d70a94c3b2e87fc6104415557203f043e87ba..2a113051315d87de105b43b91dae212884fc6a40 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -59,6 +59,12 @@ public class I2PSnarkServlet extends HttpServlet {
         _manager.start();
     }
     
+    @Override
+    public void destroy() {
+        _manager.stop();
+        super.destroy();
+    }
+
     @Override
     public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         req.setCharacterEncoding("UTF-8");
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java
index de71ff3b2fdd43336b2bf70e3dbc6007700c3fa2..af4049a21b09183a70f2e84f4d588873e937535a 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java
@@ -124,13 +124,13 @@ class HTTPResponseOutputStream extends FilterOutputStream {
      * Tweak that first HTTP response line (HTTP 200 OK, etc)
      *
      */
-    protected String filterResponseLine(String line) {
+    protected static String filterResponseLine(String line) {
         return line;
     }
     
     /** we ignore any potential \r, since we trim it on write anyway */
     private static final byte NL = '\n';
-    private boolean isNL(byte b) { return (b == NL); }
+    private static boolean isNL(byte b) { return (b == NL); }
     
     /** ok, received, now munge & write it */
     private void writeHeader() throws IOException {
@@ -275,7 +275,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
             _context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, end-start);
         }
     }
-    private class InternalGZIPInputStream extends GZIPInputStream {
+
+    private static class InternalGZIPInputStream extends GZIPInputStream {
         public InternalGZIPInputStream(InputStream in) throws IOException {
             super(in);
         }
@@ -318,6 +319,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
         return super.toString() + ": " + _in;
     }
     
+/*******
     public static void main(String args[]) {
         String simple   = "HTTP/1.1 200 OK\n" +
                           "foo: bar\n" +
@@ -367,7 +369,6 @@ class HTTPResponseOutputStream extends FilterOutputStream {
                           "A:\n" +
                           "\n";
         
-        /* */
         test("Simple", simple, true);
         test("Filtered", filtered, true);
         test("Filtered windows", winfilter, true);
@@ -382,7 +383,6 @@ class HTTPResponseOutputStream extends FilterOutputStream {
         test("Invalid (bad headers)", invalid5, true);
         test("Invalid (bad headers2)", invalid6, false);
         test("Invalid (bad headers3)", invalid7, false);
-        /* */
     }
     
     private static void test(String name, String orig, boolean shouldPass) {
@@ -401,4 +401,5 @@ class HTTPResponseOutputStream extends FilterOutputStream {
                 System.out.println("Properly fails with " + e.getMessage());
         }
     }
+******/
 }
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
index a00fc7d8de98f29bb01ab0a86accf40289c9b955..26190dda78172d12bb81b303ff448b16f8c26855 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
@@ -213,7 +213,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
                 sockMgr.getSession().destroySession();
             } catch (I2PException ex) {
                 _log.error("Error destroying the session", ex);
-                System.exit(1);
+                //System.exit(1);
             }
             l.log("Server shut down.");
             open = false;
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
index d6c98c52295f37150222a98c90024f3d351dc11f..91ac9726f055f0f427051d6e980a73fa05f28164 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
@@ -11,7 +11,6 @@ import java.util.Set;
 
 import net.i2p.router.startup.ClientAppConfig;
 import net.i2p.router.startup.LoadClientAppsJob;
-import net.i2p.util.Log;
 
 import org.mortbay.jetty.Server;
 
@@ -19,15 +18,19 @@ import org.mortbay.jetty.Server;
  *  Saves changes to clients.config or webapps.config
  */
 public class ConfigClientsHandler extends FormHandler {
-    private Log configClient_log;
     private Map _settings;
     
-    public ConfigClientsHandler() {
-        configClient_log = ContextHelper.getContext(null).logManager().getLog(ConfigClientsHandler.class);
-    }
-
     @Override
     protected void processForm() {
+        // set action for when CR is hit in a text input box
+        if (_action.length() <= 0) {
+            String url = getJettyString("pluginURL");
+            if (url != null && url.length() > 0)
+                _action = "Install Plugin";
+            else
+                _action = "Save Client Configuration";
+        }
+
         if (_action.equals(_("Save Client Configuration"))) {
             saveClientChanges();
             return;
@@ -200,7 +203,7 @@ public class ConfigClientsHandler extends FormHandler {
             return;
         }
         ClientAppConfig ca = clients.get(i);
-        LoadClientAppsJob.runClient(ca.className, ca.clientName, LoadClientAppsJob.parseArgs(ca.args), configClient_log);
+        LoadClientAppsJob.runClient(ca.className, ca.clientName, LoadClientAppsJob.parseArgs(ca.args), _log);
         addFormNotice(_("Client") + ' ' + _(ca.clientName) + ' ' + _("started") + '.');
     }
 
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
index a897f84e407f2b604e795672b2e15154aea4629b..9dbf4de23a6552a1c69bfb73533f6850fbf428ca 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
@@ -43,11 +43,11 @@ public class ConfigClientsHelper extends HelperBase {
             renderForm(buf, ""+cur, ca.clientName, false, !ca.disabled,
                        "webConsole".equals(ca.clientName) || "Web console".equals(ca.clientName),
                        ca.className + ((ca.args != null) ? " " + ca.args : ""), (""+cur).equals(_edit),
-                       true, false, false, true);
+                       true, false, false, true, !ca.disabled);
         }
         
         if ("new".equals(_edit))
-            renderForm(buf, "" + clients.size(), "", false, false, false, "", true, false, false, false, false);
+            renderForm(buf, "" + clients.size(), "", false, false, false, "", true, false, false, false, false, false);
         buf.append("</table>\n");
         return buf.toString();
     }
@@ -65,7 +65,7 @@ public class ConfigClientsHelper extends HelperBase {
                 String val = props.getProperty(name);
                 renderForm(buf, app, app, !"addressbook".equals(app),
                            "true".equals(val), RouterConsoleRunner.ROUTERCONSOLE.equals(app), app + ".war",
-                           false, false, false, false, false);
+                           false, false, false, false, false, true);
             }
         }
         buf.append("</table>\n");
@@ -149,7 +149,7 @@ public class ConfigClientsHelper extends HelperBase {
                 boolean enableStop = !Boolean.valueOf(appProps.getProperty("disableStop")).booleanValue();
                 renderForm(buf, app, app, false,
                            "true".equals(val), false, desc.toString(), false, false,
-                           updateURL != null, enableStop, true);
+                           updateURL != null, enableStop, true, true);
             }
         }
         buf.append("</table>\n");
@@ -160,7 +160,7 @@ public class ConfigClientsHelper extends HelperBase {
     private void renderForm(StringBuilder buf, String index, String name, boolean urlify,
                             boolean enabled, boolean ro, String desc, boolean edit,
                             boolean showEditButton, boolean showUpdateButton, boolean showStopButton,
-                            boolean showDeleteButton) {
+                            boolean showDeleteButton, boolean showStartButton) {
         buf.append("<tr><td class=\"mediumtags\" align=\"right\" width=\"25%\">");
         if (urlify && enabled) {
             String link = "/";
@@ -183,7 +183,7 @@ public class ConfigClientsHelper extends HelperBase {
                 buf.append("disabled=\"true\" ");
         }
         buf.append("></td><td align=\"center\" width=\"15%\">");
-        if ((!enabled) && !edit) {
+        if (showStartButton && (!ro) && !edit) {
             buf.append("<button type=\"submit\" name=\"action\" value=\"Start ").append(index).append("\" >" + _("Start") + "<span class=hide> ").append(index).append("</span></button>");
         }
         if (showEditButton && (!edit) && !ro)
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java
index df7dc6697ee817134b96b762709ec359aacb3d19..844a76cb203b6b532f4ab2bfdf1827894891d030 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java
@@ -1,6 +1,8 @@
 package net.i2p.router.web;
 
-import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -31,9 +33,12 @@ public class NavHelper {
         if (_apps.isEmpty())
             return "";
         StringBuilder buf = new StringBuilder(256); 
-        for (Iterator<String> iter = _apps.keySet().iterator(); iter.hasNext(); ) {
-            String name = iter.next();
+        List<String> l = new ArrayList(_apps.keySet());
+        Collections.sort(l);
+        for (String name : l) {
             String path = _apps.get(name);
+            if (path == null)
+                continue;
             buf.append(" <a target=\"_top\" href=\"").append(path).append("\">");
             buf.append(name).append("</a>");
         }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
index 3497bc86751e0f7a115ab442ad88a325e2f73434..effcca6bf02b0ca4c64e6a7e4f9d1d480e38f886 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
@@ -286,11 +286,11 @@ public class NetDbRenderer {
             buf.append("<b>").append(DataHelper.stripHTML(style)).append(":</b> ");
             int cost = addr.getCost();
             if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10)))
-                buf.append('[').append("cost").append('=').append("" + cost).append("] ");
+                buf.append('[').append(_("cost")).append('=').append("" + cost).append("] ");
             for (Iterator optIter = addr.getOptions().keySet().iterator(); optIter.hasNext(); ) {
                 String name = (String)optIter.next();
                 String val = addr.getOptions().getProperty(name);
-                buf.append('[').append(DataHelper.stripHTML(name)).append('=').append(DataHelper.stripHTML(val)).append("] ");
+                buf.append('[').append(_(DataHelper.stripHTML(name))).append('=').append(DataHelper.stripHTML(val)).append("] ");
             }
         }
         buf.append("</td></tr>\n");
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
index 1e14b8917d7c28487e4cbe25a39a0eddd268a3d2..cdeaaeb9350a05efd22978f7a924cfd26edaaeb6 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
@@ -35,7 +35,7 @@ import org.mortbay.jetty.Server;
  *  @author zzz
  */
 public class PluginStarter implements Runnable {
-    private RouterContext _context;
+    protected RouterContext _context;
     static final String PREFIX = "plugin.";
     static final String ENABLED = ".startOnLoad";
     private static final String[] STANDARD_WEBAPPS = { "i2psnark", "i2ptunnel", "susidns",
@@ -223,7 +223,8 @@ public class PluginStarter implements Runnable {
         if (name != null && name.length() > 0)
             NavHelper.unregisterApp(name);
 
-        log.error("Stopping plugin: " + appName);
+        if (log.shouldLog(Log.WARN))
+            log.warn("Stopping plugin: " + appName);
         return true;
     }
 
@@ -426,7 +427,8 @@ public class PluginStarter implements Runnable {
             }
             try {
                 addPath(f.toURI().toURL());
-                log.error("INFO: Adding plugin to classpath: " + f);
+                if (log.shouldLog(Log.WARN))
+                    log.warn("INFO: Adding plugin to classpath: " + f);
             } catch (Exception e) {
                 log.error("Plugin client " + clientName + " bad classpath element: " + f, e);
             }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStopper.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStopper.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f29eddf273e66f67b0266fc7ae8d7e360667292
--- /dev/null
+++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStopper.java
@@ -0,0 +1,40 @@
+package net.i2p.router.web;
+
+import net.i2p.router.RouterContext;
+import net.i2p.util.Log;
+
+/**
+ *  Stop all plugins that are installed
+ *
+ *  @since 0.7.13
+ *  @author zzz
+ */
+public class PluginStopper extends PluginStarter {
+
+    public PluginStopper(RouterContext ctx) {
+        super(ctx);
+    }
+
+    @Override
+    public void run() {
+        stopPlugins(_context);
+    }
+
+    /**
+     *  Stop all plugins
+     *  (whether or not they were ever started)
+     *
+     *  this shouldn't throw anything
+     */
+    static void stopPlugins(RouterContext ctx) {
+        Log log = ctx.logManager().getLog(PluginStopper.class);
+        for (String app : getPlugins()) {
+            try {
+               stopPlugin(ctx, app);
+            } catch (Throwable e) {
+               if (log.shouldLog(Log.WARN))
+                   log.warn("Failed to stop plugin: " + app, e);
+            }
+        }
+    }
+}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateChecker.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateChecker.java
index 8645df585559f8ef83581766cceb1401fcece6cc..9ffc903f06eab51891ede050d0e640f2e6dbdd62 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateChecker.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateChecker.java
@@ -4,6 +4,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.util.List;
 import java.util.Properties;
 
 import net.i2p.I2PAppContext;
@@ -14,6 +15,8 @@ import net.i2p.util.EepGet;
 import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
 import net.i2p.util.PartialEepGet;
+import net.i2p.util.SimpleScheduler;
+import net.i2p.util.SimpleTimer;
 import net.i2p.util.VersionComparator;
 
 /**
@@ -46,6 +49,20 @@ public class PluginUpdateChecker extends UpdateHandler {
         super(ctx);
     }
     
+    /** check all plugins */
+    public void update() {
+        Thread t = new I2PAppThread(new AllCheckerRunner(), "AllAppChecker", true);
+        t.start();
+    }
+
+    public class AllCheckerRunner implements Runnable {
+        public void run() {
+            List<String> plugins = PluginStarter.getPlugins();
+            // TODO
+        }
+    }
+
+    /** check a single plugin */
     public void update(String appName) {
         // don't block waiting for the other one to finish
         if ("true".equals(System.getProperty(PROP_UPDATE_IN_PROGRESS))) {
@@ -84,6 +101,21 @@ public class PluginUpdateChecker extends UpdateHandler {
         return false;
     }
     
+    private void scheduleStatusClean(String msg) {
+        SimpleScheduler.getInstance().addEvent(new Cleaner(msg), 60*60*1000);
+    }
+
+    private class Cleaner implements SimpleTimer.TimedEvent {
+        private String _msg;
+        public Cleaner(String msg) {
+            _msg = msg;
+        }
+        public void timeReached() {
+            if (_msg.equals(getStatus()))
+                updateStatus("");
+        }
+    }
+
     public class PluginUpdateCheckerRunner extends UpdateRunner implements Runnable, EepGet.StatusListener {
         ByteArrayOutputStream _baos;
 
@@ -116,17 +148,22 @@ public class PluginUpdateChecker extends UpdateHandler {
         public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
             String newVersion = TrustedUpdate.getVersionString(new ByteArrayInputStream(_baos.toByteArray()));
             boolean newer = (new VersionComparator()).compare(newVersion, _oldVersion) > 0;
+            String msg;
             if (newer)
-                updateStatus("<b>" + _("New plugin version {0} is available", newVersion) + "</b>");
+                msg = "<b>" + _("New plugin version {0} is available", newVersion) + "</b>";
             else
-                updateStatus("<b>" + _("No new version is available for plugin {0}", _appName) + "</b>");
+                msg = "<b>" + _("No new version is available for plugin {0}", _appName) + "</b>";
+            updateStatus(msg);
+            scheduleStatusClean(msg);
         }
 
         @Override
         public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
             File f = new File(_updateFile);
             f.delete();
-            updateStatus("<b>" + _("Update check failed for plugin {0}", _appName) + "</b>");
+            String msg = "<b>" + _("Update check failed for plugin {0}", _appName) + "</b>";
+            updateStatus(msg);
+            scheduleStatusClean(msg);
         }
     }
 }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java
index f8e0f3aa4068eab700c05850eea5255a50f78c5b..e028ee3cdc53f010e722f8465c8c5ef0ed53cfc6 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java
@@ -16,6 +16,8 @@ import net.i2p.util.FileUtil;
 import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
 import net.i2p.util.OrderedProperties;
+import net.i2p.util.SimpleScheduler;
+import net.i2p.util.SimpleTimer;
 import net.i2p.util.VersionComparator;
 
 /**
@@ -89,6 +91,21 @@ public class PluginUpdateHandler extends UpdateHandler {
         return false;
     }
     
+    private void scheduleStatusClean(String msg) {
+        SimpleScheduler.getInstance().addEvent(new Cleaner(msg), 60*60*1000);
+    }
+
+    private class Cleaner implements SimpleTimer.TimedEvent {
+        private String _msg;
+        public Cleaner(String msg) {
+            _msg = msg;
+        }
+        public void timeReached() {
+            if (_msg.equals(getStatus()))
+                updateStatus("");
+        }
+    }
+
     public class PluginUpdateRunner extends UpdateRunner implements Runnable, EepGet.StatusListener {
 
         public PluginUpdateRunner(String url) { 
@@ -136,7 +153,7 @@ public class PluginUpdateHandler extends UpdateHandler {
             File appDir = new File(_context.getAppDir(), PLUGIN_DIR);
             if ((!appDir.exists()) && (!appDir.mkdir())) {
                 f.delete();
-                updateStatus("<b>" + _("Cannot create plugin directory {0}", appDir.getAbsolutePath()) + "</b>");
+                statusDone("<b>" + _("Cannot create plugin directory {0}", appDir.getAbsolutePath()) + "</b>");
                 return;
             }
 
@@ -145,7 +162,7 @@ public class PluginUpdateHandler extends UpdateHandler {
             // extract to a zip file whether the sig is good or not, so we can get the properties file
             String err = up.migrateFile(f, to);
             if (err != null) {
-                updateStatus("<b>" + err + ' ' + _("from {0}", url) + " </b>");
+                statusDone("<b>" + err + ' ' + _("from {0}", url) + " </b>");
                 f.delete();
                 to.delete();
                 return;
@@ -155,7 +172,7 @@ public class PluginUpdateHandler extends UpdateHandler {
                 f.delete();
                 to.delete();
                 FileUtil.rmdir(tempDir, false);
-                updateStatus("<b>" + _("Plugin from {0} is corrupt", url) + "</b>");
+                statusDone("<b>" + _("Plugin from {0} is corrupt", url) + "</b>");
                 return;
             }
             File installProps = new File(tempDir, "plugin.config");
@@ -166,7 +183,7 @@ public class PluginUpdateHandler extends UpdateHandler {
                 f.delete();
                 to.delete();
                 FileUtil.rmdir(tempDir, false);
-                updateStatus("<b>" + _("Plugin from {0} does not contain the required configuration file", url) + "</b>");
+                statusDone("<b>" + _("Plugin from {0} does not contain the required configuration file", url) + "</b>");
                 return;
             }
             // we don't need this anymore, we will unzip again
@@ -179,7 +196,7 @@ public class PluginUpdateHandler extends UpdateHandler {
                 f.delete();
                 to.delete();
                 //updateStatus("<b>" + "Plugin contains an invalid key" + ' ' + pubkey + ' ' + signer + "</b>");
-                updateStatus("<b>" + _("Plugin from {0} contains an invalid key", url) + "</b>");
+                statusDone("<b>" + _("Plugin from {0} contains an invalid key", url) + "</b>");
                 return;
             }
 
@@ -198,7 +215,7 @@ public class PluginUpdateHandler extends UpdateHandler {
                 if (!signer.equals(signingKeyName)) {
                     f.delete();
                     to.delete();
-                    updateStatus("<b>" + _("Plugin signature verification of {0} failed", url) + "</b>");
+                    statusDone("<b>" + _("Plugin signature verification of {0} failed", url) + "</b>");
                     return;
                 }
             } else {
@@ -207,7 +224,7 @@ public class PluginUpdateHandler extends UpdateHandler {
                     // bad or duplicate key
                     f.delete();
                     to.delete();
-                    updateStatus("<b>" + _("Plugin signature verification of {0} failed", url) + "</b>");
+                    statusDone("<b>" + _("Plugin signature verification of {0} failed", url) + "</b>");
                     return;
                 }
                 // ...and try the verify again
@@ -216,7 +233,7 @@ public class PluginUpdateHandler extends UpdateHandler {
                 if (!signer.equals(signingKeyName)) {
                     f.delete();
                     to.delete();
-                    updateStatus("<b>" + _("Plugin signature verification of {0} failed", url) + "</b>");
+                    statusDone("<b>" + _("Plugin signature verification of {0} failed", url) + "</b>");
                     return;
                 }
             }
@@ -231,12 +248,12 @@ public class PluginUpdateHandler extends UpdateHandler {
                 version.indexOf("<") >= 0 || version.indexOf(">") >= 0 ||
                 appName.startsWith(".") || appName.indexOf("/") >= 0 || appName.indexOf("\\") >= 0) {
                 to.delete();
-                updateStatus("<b>" + _("Plugin from {0} has invalid name or version", url) + "</b>");
+                statusDone("<b>" + _("Plugin from {0} has invalid name or version", url) + "</b>");
                 return;
             }
             if (!version.equals(sudVersion)) {
                 to.delete();
-                updateStatus("<b>" + _("Plugin {0} has mismatched versions", appName) + "</b>");
+                statusDone("<b>" + _("Plugin {0} has mismatched versions", appName) + "</b>");
                 return;
             }
 
@@ -244,7 +261,7 @@ public class PluginUpdateHandler extends UpdateHandler {
             if (minVersion != null &&
                 (new VersionComparator()).compare(CoreVersion.VERSION, minVersion) < 0) {
                 to.delete();
-                updateStatus("<b>" + _("This plugin requires I2P version {0} or higher", minVersion) + "</b>");
+                statusDone("<b>" + _("This plugin requires I2P version {0} or higher", minVersion) + "</b>");
                 return;
             }
 
@@ -252,7 +269,7 @@ public class PluginUpdateHandler extends UpdateHandler {
             if (minVersion != null &&
                 (new VersionComparator()).compare(System.getProperty("java.version"), minVersion) < 0) {
                 to.delete();
-                updateStatus("<b>" + _("This plugin requires Java version {0} or higher", minVersion) + "</b>");
+                statusDone("<b>" + _("This plugin requires Java version {0} or higher", minVersion) + "</b>");
                 return;
             }
 
@@ -260,7 +277,7 @@ public class PluginUpdateHandler extends UpdateHandler {
             if (destDir.exists()) {
                 if (Boolean.valueOf(props.getProperty("install-only")).booleanValue()) {
                     to.delete();
-                    updateStatus("<b>" + _("Downloaded plugin is for new installs only, but the plugin is already installed", url) + "</b>");
+                    statusDone("<b>" + _("Downloaded plugin is for new installs only, but the plugin is already installed", url) + "</b>");
                     return;
                 }
 
@@ -272,7 +289,7 @@ public class PluginUpdateHandler extends UpdateHandler {
                 } catch (IOException ioe) {
                     to.delete();
                     FileUtil.rmdir(tempDir, false);
-                    updateStatus("<b>" + _("Installed plugin does not contain the required configuration file", url) + "</b>");
+                    statusDone("<b>" + _("Installed plugin does not contain the required configuration file", url) + "</b>");
                     return;
                 }
                 String oldPubkey = oldProps.getProperty("key");
@@ -280,28 +297,28 @@ public class PluginUpdateHandler extends UpdateHandler {
                 String oldAppName = props.getProperty("name");
                 if ((!pubkey.equals(oldPubkey)) || (!signer.equals(oldKeyName)) || (!appName.equals(oldAppName))) {
                     to.delete();
-                    updateStatus("<b>" + _("Signature of downloaded plugin does not match installed plugin") + "</b>");
+                    statusDone("<b>" + _("Signature of downloaded plugin does not match installed plugin") + "</b>");
                     return;
                 }
                 String oldVersion = oldProps.getProperty("version");
                 if (oldVersion == null ||
                     (new VersionComparator()).compare(oldVersion, version) >= 0) {
                     to.delete();
-                    updateStatus("<b>" + _("Downloaded plugin version {0} is not newer than installed plugin", version) + "</b>");
+                    statusDone("<b>" + _("Downloaded plugin version {0} is not newer than installed plugin", version) + "</b>");
                     return;
                 }
                 minVersion = ConfigClientsHelper.stripHTML(props, "min-installed-version");
                 if (minVersion != null &&
                     (new VersionComparator()).compare(minVersion, oldVersion) > 0) {
                     to.delete();
-                    updateStatus("<b>" + _("Plugin update requires installed plugin version {0} or higher", minVersion) + "</b>");
+                    statusDone("<b>" + _("Plugin update requires installed plugin version {0} or higher", minVersion) + "</b>");
                     return;
                 }
                 String maxVersion = ConfigClientsHelper.stripHTML(props, "max-installed-version");
                 if (maxVersion != null &&
                     (new VersionComparator()).compare(maxVersion, oldVersion) < 0) {
                     to.delete();
-                    updateStatus("<b>" + _("Plugin update requires installed plugin version {0} or lower", maxVersion) + "</b>");
+                    statusDone("<b>" + _("Plugin update requires installed plugin version {0} or lower", maxVersion) + "</b>");
                     return;
                 }
 
@@ -318,12 +335,12 @@ public class PluginUpdateHandler extends UpdateHandler {
             } else {
                 if (Boolean.valueOf(props.getProperty("update-only")).booleanValue()) {
                     to.delete();
-                    updateStatus("<b>" + _("Plugin is for upgrades only, but the plugin is not installed") + "</b>");
+                    statusDone("<b>" + _("Plugin is for upgrades only, but the plugin is not installed") + "</b>");
                     return;
                 }
                 if (!destDir.mkdir()) {
                     to.delete();
-                    updateStatus("<b>" + _("Cannot create plugin directory {0}", destDir.getAbsolutePath()) + "</b>");
+                    statusDone("<b>" + _("Cannot create plugin directory {0}", destDir.getAbsolutePath()) + "</b>");
                     return;
                 }
             }
@@ -331,16 +348,16 @@ public class PluginUpdateHandler extends UpdateHandler {
             // Finally, extract the zip to the plugin directory
             if (!FileUtil.extractZip(to, destDir)) {
                 to.delete();
-                updateStatus("<b>" + _("Failed to install plugin in {0}", destDir.getAbsolutePath()) + "</b>");
+                statusDone("<b>" + _("Failed to install plugin in {0}", destDir.getAbsolutePath()) + "</b>");
                 return;
             }
 
             to.delete();
             if (Boolean.valueOf(props.getProperty("dont-start-at-install")).booleanValue()) {
                 if (Boolean.valueOf(props.getProperty("router-restart-required")).booleanValue())
-                    updateStatus("<b>" + _("Plugin {0} installed, router restart required", appName) + "</b>");
+                    statusDone("<b>" + _("Plugin {0} installed, router restart required", appName) + "</b>");
                 else {
-                    updateStatus("<b>" + _("Plugin {0} installed", appName) + "</b>");
+                    statusDone("<b>" + _("Plugin {0} installed", appName) + "</b>");
                     Properties pluginProps = PluginStarter.pluginProperties();
                     pluginProps.setProperty(PluginStarter.PREFIX + appName + PluginStarter.ENABLED, "false");
                     PluginStarter.storePluginProperties(pluginProps);
@@ -349,11 +366,11 @@ public class PluginUpdateHandler extends UpdateHandler {
                 // start everything
                 try {
                     if (PluginStarter.startPlugin(_context, appName))
-                        updateStatus("<b>" + _("Plugin {0} installed and started", appName) + "</b>");
+                        statusDone("<b>" + _("Plugin {0} installed and started", appName) + "</b>");
                     else
-                        updateStatus("<b>" + _("Plugin {0} installed but failed to start, check logs", appName) + "</b>");
+                        statusDone("<b>" + _("Plugin {0} installed but failed to start, check logs", appName) + "</b>");
                 } catch (Throwable e) {
-                    updateStatus("<b>" + _("Plugin {0} installed but failed to start", appName) + ": " + e + "</b>");
+                    statusDone("<b>" + _("Plugin {0} installed but failed to start", appName) + ": " + e + "</b>");
                     _log.error("Error starting plugin " + appName, e);
                 }
             }
@@ -363,8 +380,14 @@ public class PluginUpdateHandler extends UpdateHandler {
         public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
             File f = new File(_updateFile);
             f.delete();
-            updateStatus("<b>" + _("Failed to download plugin from {0}", url) + "</b>");
+            statusDone("<b>" + _("Failed to download plugin from {0}", url) + "</b>");
+        }
+
+        private void statusDone(String msg) {
+            updateStatus(msg);
+            scheduleStatusClean(msg);
         }
+
     }
     
     @Override
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
index ef5d2846c238a7260b74972a43e449e03c9868c9..460db8d1765679bd4c2684d85fefdfc9b660c418 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
@@ -190,9 +190,11 @@ public class RouterConsoleRunner {
         
         List<RouterContext> contexts = RouterContext.listContexts();
         if (contexts != null) {
-            if (PluginStarter.pluginsEnabled(contexts.get(0))) {
-                t = new I2PAppThread(new PluginStarter(contexts.get(0)), "PluginStarter", true);
+            RouterContext ctx = contexts.get(0);
+            if (PluginStarter.pluginsEnabled(ctx)) {
+                t = new I2PAppThread(new PluginStarter(ctx), "PluginStarter", true);
                 t.start();
+                ctx.addShutdownTask(new PluginStopper(ctx));
             }
         }
     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java
index 048ef31cc67619bcb43e0c92f470df9207c4ec94..8a146f5b1ba05b1683f423b322e59893f4afcd85 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java
@@ -43,9 +43,17 @@ public class WebAppStarter {
 
     /**
      *  add but don't start
+     *  This is used only by RouterConsoleRunner, which adds all the webapps first
+     *  and then starts all at once.
      */
     static WebApplicationContext addWebApp(I2PAppContext ctx, Server server, String appName, String warPath, File tmpdir) throws IOException {
 
+        // Jetty will happily load one context on top of another without stopping
+        // the first one, so we remove any previous one here
+        try {
+            stopWebApp(server, appName);
+        } catch (Throwable t) {}
+
         WebApplicationContext wac = server.addWebApplication("/"+ appName, warPath);
         tmpdir.mkdir();
         wac.setTempDirectory(tmpdir);
@@ -64,7 +72,7 @@ public class WebAppStarter {
     }
 
     /**
-     *  stop it
+     *  stop it and remove the context
      *  @throws just about anything, caller would be wise to catch Throwable
      */
     static void stopWebApp(Server server, String appName) {
@@ -74,6 +82,9 @@ public class WebAppStarter {
             // false -> not graceful
             wac.stop(false);
         } catch (InterruptedException ie) {}
+        try {
+            server.removeContext(wac);
+        } catch (IllegalStateException ise) {}
     }
 
     /** see comments in ConfigClientsHandler */
diff --git a/apps/routerconsole/java/strings/Strings.java b/apps/routerconsole/java/strings/Strings.java
index 0805d479a119730e809bcd2d9a523af8194872d0..8d37b3b7638be04d7ba8a3317c4197da1c0177d1 100644
--- a/apps/routerconsole/java/strings/Strings.java
+++ b/apps/routerconsole/java/strings/Strings.java
@@ -71,5 +71,30 @@ class Dummy {
         _("Transport");
         _("Tunnels");
         _("udp");
+
+        // parameters in transport addresses (netdb.jsp)
+        // may or may not be worth translating
+        _("host");
+        _("key");
+        _("port");
+        // capabilities
+        _("caps");
+        // introducer host
+        _("ihost0");
+        _("ihost1");
+        _("ihost2");
+        // introducer port
+        _("iport0");
+        _("iport1");
+        _("iport2");
+        // introducer key
+        _("ikey0");
+        _("ikey1");
+        _("ikey2");
+        // introducer tag
+        _("itag0");
+        _("itag1");
+        _("itag2");
+
     }
 }
diff --git a/apps/routerconsole/jsp/configclients.jsp b/apps/routerconsole/jsp/configclients.jsp
index 4bb17007c6bf500d4dc523badff91afb5f1a6973..c9a668c79791eaf9b91b8480e7bf670264fcd700 100644
--- a/apps/routerconsole/jsp/configclients.jsp
+++ b/apps/routerconsole/jsp/configclients.jsp
@@ -30,7 +30,9 @@ button span.hide{
  <% String prev = System.getProperty("net.i2p.router.web.ConfigClientsHandler.nonce");
     if (prev != null) System.setProperty("net.i2p.router.web.ConfigClientsHandler.noncePrev", prev);
     System.setProperty("net.i2p.router.web.ConfigClientsHandler.nonce", new java.util.Random().nextLong()+""); %>
- <input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigClientsHandler.nonce")%>" />
+ <input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigClientsHandler.nonce")%>" >
+ <% /* set hidden default */ %>
+ <button type="submit" name="action" value="" style="display:none" >Cancel</button>
  <h3><%=intl._("Client Configuration")%></h3><p>
  <%=intl._("The Java clients listed below are started by the router and run in the same JVM.")%>
  </p><div class="wideload">
@@ -56,7 +58,7 @@ button span.hide{
  <input type="submit" name="action" value="<%=intl._("Save WebApp Configuration")%>" />
 </div></div>
 <% if (clientshelper.showPlugins()) { %>
-<h3><a name="webapp"></a><%=intl._("Plugin Configuration")%></h3><p>
+<h3><a name="pconfig"></a><%=intl._("Plugin Configuration")%></h3><p>
  <%=intl._("The plugins listed below are started by the webConsole client.")%>
  </p><div class="wideload"><p>
  <jsp:getProperty name="clientshelper" property="form3" />
diff --git a/apps/routerconsole/jsp/configupdate.jsp b/apps/routerconsole/jsp/configupdate.jsp
index 017b7ba1579dd9fc1bfc2dc71b64ec35a10772da..22432f60ac3c65be55f72a77d353637d39c5eb84 100644
--- a/apps/routerconsole/jsp/configupdate.jsp
+++ b/apps/routerconsole/jsp/configupdate.jsp
@@ -25,7 +25,9 @@
  <% String prev = System.getProperty("net.i2p.router.web.ConfigUpdateHandler.nonce");
     if (prev != null) System.setProperty("net.i2p.router.web.ConfigUpdateHandler.noncePrev", prev);
     System.setProperty("net.i2p.router.web.ConfigUpdateHandler.nonce", new java.util.Random().nextLong()+""); %>
- <input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigUpdateHandler.nonce")%>" />
+ <input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigUpdateHandler.nonce")%>" >
+ <% /* set hidden default */ %>
+ <input type="submit" name="action" value="" style="display:none" >
       <h3><%=intl._("Check for I2P and news updates")%></h3>
       <div class="wideload"><table border="0" cellspacing="5">
         <tr><td colspan="2"></tr>
@@ -59,6 +61,6 @@
     <% }   // if canInstall %>
         <tr class="tablefooter"><td colspan="2">
         <div class="formaction">
-            <input type="submit" name="action" value="<%=intl._("Save")%>" />
-            <input type="reset" value="<%=intl._("Cancel")%>" />
+            <input type="reset" value="<%=intl._("Cancel")%>" >
+            <input type="submit" name="action" value="<%=intl._("Save")%>" >
         </div></td></tr></table></div></form></div></div></body></html>
diff --git a/build.xml b/build.xml
index 911c75d015b796d4c825093cfa61ca4c19d26dfd..df30347ddc9f83242788074aac55e7cf7c39a64d 100644
--- a/build.xml
+++ b/build.xml
@@ -264,8 +264,30 @@
         </delete>
     </target>
 
-    <target name="preppkg" depends="preppkg-linux, buildexe">
+    <target name="preppkg" depends="preppkg-linux, preppkg-windows">
         <copy file="build/jbigi.jar" todir="pkg-temp/lib" />
+        <copy todir="pkg-temp/lib/wrapper/freebsd/">
+            <fileset dir="installer/lib/wrapper/freebsd/" />
+        </copy>
+        <copy todir="pkg-temp/lib/wrapper/macosx/">
+            <fileset dir="installer/lib/wrapper/macosx/" />
+        </copy>
+        <copy todir="pkg-temp/lib/wrapper/solaris/">
+            <fileset dir="installer/lib/wrapper/solaris/" />
+        </copy>
+    </target>
+
+    <target name="preppkg-windows-only" depends="preppkg-windows">
+        <!-- rip the non-windows stuff out of jbigi.jar -->
+        <mkdir dir="tmpextract" />
+        <unjar src="build/jbigi.jar" dest="tmpextract/" />
+        <jar destfile="pkg-temp/lib/jbigi.jar" >
+            <fileset dir="tmpextract/" includes="*windows*" />
+        </jar>
+        <delete dir="tmpextract/" />
+    </target>
+
+    <target name="preppkg-windows" depends="preppkg-base, buildexe">
         <copy file="i2p.exe" todir="pkg-temp/" failonerror="false" />
         <copy file="apps/systray/java/lib/systray4j.dll" todir="pkg-temp/lib" />
         <copy file="apps/systray/java/resources/iggy.ico" todir="pkg-temp/icons" />
@@ -276,15 +298,6 @@
         <copy file="installer/resources/install_i2p_service_winnt.bat" todir="pkg-temp/" />
         <copy file="installer/resources/postinstall.bat" todir="pkg-temp/" />
         <copy file="installer/resources/uninstall_i2p_service_winnt.bat" todir="pkg-temp/" />
-        <copy todir="pkg-temp/lib/wrapper/freebsd/">
-            <fileset dir="installer/lib/wrapper/freebsd/" />
-        </copy>
-        <copy todir="pkg-temp/lib/wrapper/macosx/">
-            <fileset dir="installer/lib/wrapper/macosx/" />
-        </copy>
-        <copy todir="pkg-temp/lib/wrapper/solaris/">
-            <fileset dir="installer/lib/wrapper/solaris/" />
-        </copy>
         <copy todir="pkg-temp/lib/wrapper/win32/">
             <fileset dir="installer/lib/wrapper/win32/" />
         </copy>
@@ -301,7 +314,22 @@
         <delete dir="tmpextract/" />
     </target>
 
-    <target name="preppkg-linux" depends="build, preplicenses, prepconsoleDocs">
+    <target name="preppkg-linux" depends="preppkg-base">
+        <copy file="installer/resources/runplain.sh" todir="pkg-temp/" />
+        <copy file="apps/i2psnark/launch-i2psnark" todir="pkg-temp/" />
+        <copy file="installer/resources/eepget" todir="pkg-temp/" />
+        <copy file="installer/resources/i2prouter" todir="pkg-temp/" />
+        <copy file="installer/resources/osid" todir="pkg-temp/" />
+        <copy file="installer/resources/postinstall.sh" todir="pkg-temp/" />
+        <copy todir="pkg-temp/lib/wrapper/linux/">
+            <fileset dir="installer/lib/wrapper/linux/" />
+        </copy>
+        <copy todir="pkg-temp/lib/wrapper/linux64/">
+            <fileset dir="installer/lib/wrapper/linux64/" />
+        </copy>
+    </target>
+
+    <target name="preppkg-base" depends="build, preplicenses, prepconsoleDocs">
         <copy file="build/i2p.jar" todir="pkg-temp/lib/" />
         <copy file="build/i2ptunnel.jar" todir="pkg-temp/lib/" />
         <copy file="build/jasper-compiler.jar" todir="pkg-temp/lib/" />
@@ -320,7 +348,6 @@
         <copy file="build/BOB.jar" todir="pkg-temp/lib/" />
         <copy file="build/systray.jar" todir="pkg-temp/lib" />
         <copy file="build/i2psnark.jar" todir="pkg-temp/lib/" />
-        <copy file="installer/resources/runplain.sh" todir="pkg-temp/" />
         <copy file="apps/systray/java/lib/systray4j.jar" todir="pkg-temp/lib" />
         <copy file="build/i2ptunnel.war" todir="pkg-temp/webapps/" />
         <copy file="build/routerconsole.war" todir="pkg-temp/webapps/" />
@@ -330,24 +357,13 @@
         <copy file="apps/susidns/src/WEB-INF/lib/jstl.jar" todir="pkg-temp/lib/" />
         <copy file="apps/susidns/src/WEB-INF/lib/standard.jar" todir="pkg-temp/lib/" />
         <copy file="build/i2psnark.war" todir="pkg-temp/webapps/" />
-        <copy file="apps/i2psnark/launch-i2psnark" todir="pkg-temp/" />
         <copy file="apps/i2psnark/jetty-i2psnark.xml" todir="pkg-temp/" />
         <copy file="apps/i2psnark/i2psnark.config" todir="pkg-temp/" />
         <copy file="installer/resources/blocklist.txt" todir="pkg-temp/" />
         <copy file="installer/resources/clients.config" todir="pkg-temp/" />
-        <copy file="installer/resources/eepget" todir="pkg-temp/" />
-        <copy file="installer/resources/i2prouter" todir="pkg-temp/" />
         <copy file="installer/resources/i2ptunnel.config" todir="pkg-temp/" />
-        <copy file="installer/resources/osid" todir="pkg-temp/" />
-        <copy file="installer/resources/postinstall.sh" todir="pkg-temp/" />
         <copy file="installer/resources/systray.config" todir="pkg-temp/" />
         <copy file="installer/resources/wrapper.config" todir="pkg-temp/" />
-        <copy todir="pkg-temp/lib/wrapper/linux/">
-            <fileset dir="installer/lib/wrapper/linux/" />
-        </copy>
-        <copy todir="pkg-temp/lib/wrapper/linux64/">
-            <fileset dir="installer/lib/wrapper/linux64/" />
-        </copy>
         <copy file="installer/resources/hosts.txt" todir="pkg-temp/" />
         <copy file="INSTALL-headless.txt" todir="pkg-temp/" />
         <!-- overwrite the truncated history put in by the updater -->
diff --git a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
index ff1b367fd055ae06fad46e10b94450f5688ecf49..a6045d6a27421d35258f0e2f478b018dd544c663 100644
--- a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
+++ b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
@@ -7,7 +7,11 @@
  */
 package net.i2p.client.naming;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -87,13 +91,10 @@ public class HostsTxtNamingService extends NamingService {
         List filenames = getFilenames();
         for (int i = 0; i < filenames.size(); i++) { 
             String hostsfile = (String)filenames.get(i);
-            Properties hosts = new Properties();
             try {
                 File f = new File(_context.getRouterDir(), hostsfile);
                 if ( (f.exists()) && (f.canRead()) ) {
-                    DataHelper.loadProps(hosts, f, true);
-                    
-                    String key = hosts.getProperty(hostname.toLowerCase());
+                    String key = getKey(f, hostname.toLowerCase());
                     if ( (key != null) && (key.trim().length() > 0) ) {
                         d = lookupBase64(key);
                         putCache(hostname, d);
@@ -168,4 +169,30 @@ public class HostsTxtNamingService extends NamingService {
         }
         return null;
     }
+
+    /**
+     *  Better than DataHelper.loadProps(), doesn't load the whole file into memory,
+     *  and stops when it finds a match.
+     *
+     *  @param host lower case
+     *  @since 0.7.13
+     */
+    private static String getKey(File file, String host) throws IOException {
+        BufferedReader in = null;
+        try {
+            in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"), 16*1024);
+            String line = null;
+            while ( (line = in.readLine()) != null) {
+                if (!line.toLowerCase().startsWith(host + '='))
+                    continue;
+                if (line.indexOf('#') > 0)  // trim off any end of line comment
+                    line = line.substring(0, line.indexOf('#')).trim();
+                int split = line.indexOf('=');
+                return line.substring(split+1);   //.trim() ??????????????
+            }
+        } finally {
+            if (in != null) try { in.close(); } catch (IOException ioe) {}
+        }
+        return null;
+    }
 }
diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java
index c2c9d1c0abb89777324bea9ed2e0b6d9c2afa85d..dc445a37aeb420e2b068fb2a78c259e46314bf25 100644
--- a/core/java/src/net/i2p/data/PrivateKeyFile.java
+++ b/core/java/src/net/i2p/data/PrivateKeyFile.java
@@ -267,6 +267,8 @@ public class PrivateKeyFile {
         StringBuilder s = new StringBuilder(128);
         s.append("Dest: ");
         s.append(this.dest != null ? this.dest.toBase64() : "null");
+        s.append("\nB32: ");
+        s.append(this.dest != null ? Base32.encode(this.dest.calculateHash().getData()) + ".b32.i2p" : "null");
         s.append("\nContains: ");
         s.append(this.dest);
         s.append("\nPrivate Key: ");
diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java
index 2bfd8ccf12cfbdda1be685df762a2cc48be790b6..ca5c0d55a917bd09e204fa244e4d691dcd88e660 100644
--- a/core/java/src/net/i2p/util/EepGet.java
+++ b/core/java/src/net/i2p/util/EepGet.java
@@ -455,6 +455,8 @@ public class EepGet {
                     _listeners.get(i).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
                 if (_log.shouldLog(Log.WARN))
                     _log.warn("ERR: doFetch failed " +  ioe);
+                if (ioe instanceof MalformedURLException)
+                    _keepFetching = false;
             } finally {
                 if (_out != null) {
                     try {
diff --git a/history.txt b/history.txt
index e7edc851fe91004a0ae72b2bb8655eff8ffd37bb..b9c2c00bacbaf2ff8ceab8a77ba6d6ab6850b443 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,44 @@
+2010-04-02 zzz
+    * FloodfillPeerSelector: Adjust rankings again
+
+2010-03-31 zzz
+    * FloodfillPeerSelector: Adjust rankings to try to
+      improve LeaseSet lookups
+    * HostsTxtNamingService: Don't load the whole hosts.txt
+      into memory for every lookup
+
+2010-03-29 zzz
+    * build.xml: Prep for a windows-only pkg
+    * configclients.jsp:
+      - Always show start button for webapps and plugins
+    * configclients.jsp, configupdate.jsp:
+      - Fix submission when entering CR in a text box
+    * EepGet: Don't retry after a MalformedURLException
+    * HTTPResponseOutputStream: More static
+    * Plugins:
+      - Stop all plugins at shutdown
+      - Log tweaks
+    * WebApps:
+      - Remove the WAC after stopping it
+      - Stop a WAC before starting it to prevent dups
+      - Implement destroy() in addressbook to prevent dups
+      - Implement destroy() in i2psnark to prevent dups
+
+2010-03-25 zzz
+    * configclients.jsp: Fix dup anchor
+    * Console: Sort plugin links in summary bar
+    * i2psnark:
+      - Send numwant=0 if we don't need peers
+      - Report returned complete and incomplete counts
+        if higher than peer count
+      - Allow missing peer list
+      - Log tweaks
+    * netdb.jsp: Tag transport properties
+    * Plugins: Remove final check and install console
+      messages after a while
+    * PrivateKeyFile: Add b32 output
+    * Reseed: Add another host
+
 2010-03-18 zzz
     * Blocklist, CommSystem, FIFOBandwidth, TransportManager,
       OutNetMessage, InNetMessagePool:
@@ -48,6 +89,10 @@
         not sure why taking them from the tail "reduces latency"
       - Java 5 cleanup
 
+2010-03-17 zzz
+    * I2PTunnel: Disable nonce checking when console password set
+    * Reseed: Add another host
+
 * 2010-03-15  0.7.12 released
 
 2010-03-13 zzz
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index b8546913b6cc18d89a6649558f7117b30b9aafa8..4e28a2c8d2676ff98ee569a93ae883ef407d9461 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -13,12 +13,12 @@ import net.i2p.CoreVersion;
 /**
  * Expose a version string
  *
-n */
+ */
 public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 1;
+    public final static long BUILD = 5;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java
index 4d270424bd8ad16302a6534d30a475e645857ba2..9cacb0eed437c3da0621160adfd3579d3f4324c0 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java
@@ -112,7 +112,7 @@ class FloodfillPeerSelector extends PeerSelector {
     private static final int NO_FAIL_STORE_OK = 10*60*1000;
     private static final int NO_FAIL_STORE_GOOD = NO_FAIL_STORE_OK * 2;
     /** this must be longer than the max streaming timeout (60s) */
-    private static final int NO_FAIL_LOOKUP_OK = 2*60*1000;
+    private static final int NO_FAIL_LOOKUP_OK = 75*1000;
     private static final int NO_FAIL_LOOKUP_GOOD = NO_FAIL_LOOKUP_OK * 3;
     private static final int MAX_GOOD_RESP_TIME = 5*1000;
 
@@ -135,8 +135,11 @@ class FloodfillPeerSelector extends PeerSelector {
             maxFailRate = 100d; // disable
         }
 
+        // 8 == FNDF.MAX_TO_FLOOD + 1
+        int limit = Math.max(8, howMany);
+        limit = Math.min(limit, ffs.size());
         // split sorted list into 3 sorted lists
-        for (int i = 0; found < howMany && i < ffs.size(); i++) {
+        for (int i = 0; found < howMany && i < limit; i++) {
             Hash entry = sorted.first();
             sorted.remove(entry);
             if (entry == null)
diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
index ca651bd49dbb8ef983f83a8e95222533baa74ac5..f38be9e14b4e3d230a9ad7a0edf87866b97d91a2 100644
--- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
+++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
@@ -36,7 +36,10 @@ public class Reseeder {
     // Reject unreasonably big files, because we download into a ByteArrayOutputStream.
     private static final long MAX_RESEED_RESPONSE_SIZE = 1024 * 1024;
 
-    private static final String DEFAULT_SEED_URL = "http://a.netdb.i2p2.de/,http://b.netdb.i2p2.de/,http://c.netdb.i2p2.de/,http://reseed.i2p-projekt.de/,http://i2pbote.net/netDb/,http://r31453.ovh.net/static_media/netDb/";
+    private static final String DEFAULT_SEED_URL =
+              "http://a.netdb.i2p2.de/,http://b.netdb.i2p2.de/,http://c.netdb.i2p2.de/," +
+              "http://reseed.i2p-projekt.de/,http://i2pbote.net/netDb/,http://r31453.ovh.net/static_media/netDb/," +
+              "http://p2i.mine.nu/netDb/";
     private static final String PROP_INPROGRESS = "net.i2p.router.web.ReseedHandler.reseedInProgress";
     private static final String PROP_ERROR = "net.i2p.router.web.ReseedHandler.errorMessage";
     private static final String PROP_STATUS = "net.i2p.router.web.ReseedHandler.statusMessage";