diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
index f23765b21ec8407f0fc7698a7b3764628fb80151..ddead3a58f87fa3cc951d5c64b2a94cb720facfd 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
@@ -1002,9 +1002,9 @@ public class Storage
           // TODO alternative - check hash on the fly as we write to the file,
           // to save another I/O pass
           boolean correctHash = metainfo.checkPiece(pp);
-          if (listener != null)
-            listener.storageChecked(this, piece, correctHash);
           if (!correctHash) {
+              if (listener != null)
+                  listener.storageChecked(this, piece, false);
               return false;
           }
 
@@ -1066,6 +1066,9 @@ public class Storage
             complete = needed == 0;
           }
       }
+    // tell listener after counts are updated
+    if (listener != null)
+        listener.storageChecked(this, piece, true);
 
     if (complete) {
       // do we also need to close all of the files and reopen
@@ -1228,7 +1231,8 @@ public class Storage
           String hex = DataHelper.toString(meta.getInfoHash());
           System.out.println("Created:     " + file);
           System.out.println("InfoHash:    " + hex);
-          String magnet = MagnetURI.MAGNET_FULL + hex;
+          String basename = base.getName().replace(" ", "%20");
+          String magnet = MagnetURI.MAGNET_FULL + hex + "&dn=" + basename;
           if (announce != null)
               magnet += "&tr=" + announce;
           System.out.println("Magnet:      " + magnet);
diff --git a/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java b/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java
index 759026edca967e9bd8fd23fb1e94c1dde870a5af..63d9670f177c251f31557d20fc76c35e0509e16d 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java
@@ -33,6 +33,7 @@ class UpdateRunner implements UpdateTask, CompleteListener {
     private static final long MAX_LENGTH = 30*1024*1024;
     private static final long METAINFO_TIMEOUT = 30*60*1000;
     private static final long COMPLETE_TIMEOUT = 3*60*60*1000;
+    private static final long CHECK_INTERVAL = 3*60*1000;
 
     public UpdateRunner(I2PAppContext ctx, UpdateManager umgr, SnarkManager smgr,
                         List<URI> uris, String newVersion) { 
@@ -92,6 +93,12 @@ class UpdateRunner implements UpdateTask, CompleteListener {
                          if (storage != null && storage.complete())
                              processComplete(_snark);
                     }
+                    if (!_isComplete) {
+                        if (_snark.isStopped() && !_snark.isStarting())
+                            _snark.startTorrent();
+                        // we aren't a listener so we must poll
+                        new Watcher();
+                    }
                     break;
                 }
                 String name = magnet.getName();
@@ -142,6 +149,36 @@ class UpdateRunner implements UpdateTask, CompleteListener {
         }
     }
 
+    /**
+     *  Rarely used - only if the user added the torrent, so
+     *  we aren't a complete listener.
+     *  This will periodically until the complete timeout.
+     */
+    private class Watcher extends SimpleTimer2.TimedEvent {
+        private final long _start = _context.clock().now();
+
+        public Watcher() {
+            super(_context.simpleTimer2(), CHECK_INTERVAL);
+        }
+
+        public void timeReached() {
+            if (_hasMetaInfo && _snark.getRemainingLength() == 0 && !_isComplete)
+                processComplete(_snark);
+            if (_isComplete || !_isRunning)
+                return;
+            if (_context.clock().now() - _start >= METAINFO_TIMEOUT && !_hasMetaInfo) {
+                fatal("Metainfo timeout");
+                return;
+            }
+            if (_context.clock().now() - _start >= COMPLETE_TIMEOUT) {
+                fatal("Complete timeout");
+                return;
+            }
+            notifyProgress();
+            reschedule(CHECK_INTERVAL);
+        }
+    }
+
     private void fatal(String error) {
             if (_snark != null) {
                 if (_hasMetaInfo) {
@@ -183,6 +220,15 @@ class UpdateRunner implements UpdateTask, CompleteListener {
         _isComplete = true;
     }
 
+    private void notifyProgress() {
+        if (_hasMetaInfo) {
+            long total = _snark.getTotalLength();
+            long remaining = _snark.getRemainingLength(); 
+            String status = "<b>" + _smgr.util().getString("Updating") + "</b>";
+            _umgr.notifyProgress(this, status, total - remaining, total);
+        }
+    }
+
     //////// begin CompleteListener methods
     //////// all pass through to SnarkManager
 
@@ -217,6 +263,7 @@ class UpdateRunner implements UpdateTask, CompleteListener {
             return null;
         }
         _hasMetaInfo = true;
+        notifyProgress();
         return _smgr.gotMetaInfo(snark);
     }
 
@@ -230,12 +277,7 @@ class UpdateRunner implements UpdateTask, CompleteListener {
     }
 
     public void gotPiece(Snark snark) {
-        if (_hasMetaInfo) {
-            long total = snark.getTotalLength();
-            long remaining = snark.getRemainingLength(); 
-            String status = "<b>" + _smgr.util().getString("Updating") + "</b>";
-            _umgr.notifyProgress(this, status, total - remaining, total);
-        }
+        notifyProgress();
         _smgr.gotPiece(snark);
     }
 
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
index 846ee90b9263954afaca80cd7c3b5cf1bfac7e27..3aed6631533b7853db7c07b8a312d6311eea2f7d 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
@@ -111,8 +111,10 @@ public class ConsoleUpdateManager implements UpdateManager {
 
         _context.registerUpdateManager(this);
         DummyHandler dh = new DummyHandler(_context, this);
-        register((Checker)dh, TYPE_DUMMY, HTTP, 0);
-        register((Updater)dh, TYPE_DUMMY, HTTP, 0);
+        register((Checker)dh, TYPE_DUMMY, METHOD_DUMMY, 0);
+        register((Updater)dh, TYPE_DUMMY, METHOD_DUMMY, 0);
+        VersionAvailable dummyVA = new VersionAvailable("", "", METHOD_DUMMY, Collections.EMPTY_LIST);
+        _available.put(new UpdateItem(TYPE_DUMMY, ""), dummyVA);
         // register news before router, so we don't fire off an update
         // right at instantiation if the news is already indicating a new version
         Checker c = new NewsHandler(_context, this);
@@ -120,6 +122,9 @@ public class ConsoleUpdateManager implements UpdateManager {
         register(c, ROUTER_SIGNED, HTTP, 0);  // news is an update checker for the router
         Updater u = new UpdateHandler(_context, this);
         register(u, ROUTER_SIGNED, HTTP, 0);
+        // TODO see NewsFetcher
+        //register(u, ROUTER_SIGNED, HTTPS_CLEARNET, -5);
+        //register(u, ROUTER_SIGNED, HTTP_CLEARNET, -10);
         UnsignedUpdateHandler uuh = new UnsignedUpdateHandler(_context, this);
         register((Checker)uuh, ROUTER_UNSIGNED, HTTP, 0);
         register((Updater)uuh, ROUTER_UNSIGNED, HTTP, 0);
@@ -735,6 +740,7 @@ public class ConsoleUpdateManager implements UpdateManager {
 
     /**
      *  Not necessarily the end if there are more URIs to try.
+     *  @param task checker or updater
      *  @param t may be null
      */
     public void notifyAttemptFailed(UpdateTask task, String reason, Throwable t) {
@@ -744,6 +750,7 @@ public class ConsoleUpdateManager implements UpdateManager {
 
     /**
      *  The task has finished and failed.
+     *  @param task checker or updater
      *  @param t may be null
      */
     public void notifyTaskFailed(UpdateTask task, String reason, Throwable t) {
@@ -763,8 +770,9 @@ public class ConsoleUpdateManager implements UpdateManager {
         }
         _downloaders.remove(task);
         _activeCheckers.remove(task);
-///// for certain types only
-        finishStatus("<b>" + _("Transfer failed from {0}", linkify(task.getURI().toString())) + "</b>");
+        // any other types that shouldn't display?
+        if (task.getURI() != null && task.getType() != TYPE_DUMMY)
+            finishStatus("<b>" + _("Transfer failed from {0}", linkify(task.getURI().toString())) + "</b>");
     }
 
     /**
@@ -775,7 +783,7 @@ public class ConsoleUpdateManager implements UpdateManager {
      *  If the return value is false, caller must call notifyTaskFailed() or notifyComplete()
      *  again.
      *
-     *  @param must be an Updater, not a Checker
+     *  @param task must be an Updater, not a Checker
      *  @param actualVersion may be higher (or lower?) than the version requested
      *  @param file a valid format for the task's UpdateType, or null if it did the installation itself
      *  @return true if valid, false if corrupt
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/DummyHandler.java b/apps/routerconsole/java/src/net/i2p/router/update/DummyHandler.java
index 4071efd2ec6cb943dc6adc1aac2e2bb0cc202e5b..8470d0efef6295e3d0294d4e14642fe4ce2f0a21 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/DummyHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/DummyHandler.java
@@ -55,6 +55,9 @@ class DummyHandler implements Checker, Updater {
         @Override
         public UpdateType getType() { return UpdateType.TYPE_DUMMY; }
 
+        @Override
+        public UpdateMethod getMethod() { return UpdateMethod.METHOD_DUMMY; }
+
         @Override
         protected void update() {
             try {
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
index ac9478c06082c5ff98870ec9719d33c0f9ac36dc..530926c50e986a2800c75b3cbf53cd031c6e58e4 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
@@ -114,14 +114,23 @@ class NewsFetcher extends UpdateRunner {
         }
     }
     
+    // Fake XML parsing
+    // Line must contain this, and full entry must be on one line
     private static final String VERSION_PREFIX = "<i2p.release ";
+    // all keys mapped to lower case by parseArgs()
     private static final String VERSION_KEY = "version";
-    private static final String SUD_KEY = "sudmagnet";
-    private static final String SU2_KEY = "su2magnet";
+    private static final String MIN_VERSION_KEY = "minversion";
+    private static final String SUD_KEY = "sudtorrent";
+    private static final String SU2_KEY = "su2torrent";
+    private static final String CLEARNET_SUD_KEY = "sudclearnet";
+    private static final String CLEARNET_SU2_KEY = "su2clearnet";
+    private static final String I2P_SUD_KEY = "sudi2p";
+    private static final String I2P_SU2_KEY = "su2i2p";
 
     /**
      *  Parse the installed (not the temp) news file for the latest version.
      *  TODO: Real XML parsing
+     *  TODO: Check minVersion, use backup URLs specified
      */
     void checkForUpdates() {
         FileInputStream in = null;
@@ -139,6 +148,9 @@ class NewsFetcher extends UpdateRunner {
                         if (TrustedUpdate.needsUpdate(RouterVersion.VERSION, ver)) {
                             if (_log.shouldLog(Log.DEBUG))
                                 _log.debug("Our version is out of date, update!");
+                            // TODO if minversion > our version, continue
+                            // and look for a second entry with clearnet URLs
+                            // TODO clearnet URLs, notify with HTTP_CLEARNET and/or HTTPS_CLEARNET
                             _mgr.notifyVersionAvailable(this, _currentURI,
                                                         ROUTER_SIGNED, "", HTTP,
                                                         _mgr.getUpdateURLs(ROUTER_SIGNED, "", HTTP),