diff --git a/apps/i2psnark/java/src/org/klomp/snark/ExtensionHandler.java b/apps/i2psnark/java/src/org/klomp/snark/ExtensionHandler.java
index e212f6f6139b2c42019cf7db0dc841faf4eb28c4..5b7b0b0382f1fcc38432eb536b3ba4c8e2b3b19d 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/ExtensionHandler.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/ExtensionHandler.java
@@ -21,7 +21,6 @@ import org.klomp.snark.bencode.InvalidBEncodingException;
  */
 abstract class ExtensionHandler {
 
-    private static final byte[] _handshake = buildHandshake();
     private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ExtensionHandler.class);
 
     public static final int ID_METADATA = 3;
@@ -32,17 +31,15 @@ abstract class ExtensionHandler {
 
 
   /**
+   *  @param metasize -1 if unknown
    *  @return bencoded outgoing handshake message
    */
-    public static byte[] getHandshake() {
-        return _handshake;
-    }
-
-    /** outgoing handshake message */
-    private static byte[] buildHandshake() {
+    public static byte[] getHandshake(int metasize) {
         Map<String, Object> handshake = new HashMap();
         Map<String, Integer> m = new HashMap();
         m.put(TYPE_METADATA, Integer.valueOf(ID_METADATA));
+        if (metasize >= 0)
+            handshake.put("metadata_size", Integer.valueOf(metasize));
         handshake.put("m", m);
         handshake.put("p", Integer.valueOf(6881));
         handshake.put("v", "I2PSnark");
@@ -51,6 +48,8 @@ abstract class ExtensionHandler {
     }
 
     public static void handleMessage(Peer peer, PeerListener listener, int id, byte[] bs) {
+        if (_log.shouldLog(Log.INFO))
+            _log.info("Got extension msg " + id + " length " + bs.length + " from " + peer);
         if (id == 0)
             handleHandshake(peer, listener, bs);
         else if (id == ID_METADATA)
@@ -71,10 +70,24 @@ abstract class ExtensionHandler {
             peer.setHandshakeMap(map);
             Map<String, BEValue> msgmap = map.get("m").getMap();
 
-            // rv not used, just to throw an NPE to get out of here
-            msgmap.get(TYPE_METADATA).getInt();
+            if (msgmap.get(TYPE_METADATA) == null) {
+                if (_log.shouldLog(Log.WARN))
+                    _log.debug("Peer does not support metadata extension: " + peer);
+                // drop if we need metainfo ?
+                return;
+            }
+
+            BEValue msize = map.get("metadata_size");
+            if (msize == null) {
+                if (_log.shouldLog(Log.WARN))
+                    _log.debug("Peer does not have the metainfo size yet: " + peer);
+                // drop if we need metainfo ?
+                return;
+            }
+            int metaSize = msize.getInt();
+            if (_log.shouldLog(Log.WARN))
+                _log.debug("Got the metainfo size: " + metaSize);
 
-            int metaSize = map.get("metadata_size").getInt();
             MagnetState state = peer.getMagnetState();
             int remaining;
             synchronized(state) {
@@ -83,12 +96,16 @@ abstract class ExtensionHandler {
 
                 if (state.isInitialized()) {
                     if (state.getSize() != metaSize) {
+                        if (_log.shouldLog(Log.WARN))
+                            _log.debug("Wrong metainfo size " + metaSize + " from: " + peer);
                         peer.disconnect();
                         return;
                     }
                 } else {
                     // initialize it
                     if (metaSize > MAX_METADATA_SIZE) {
+                        if (_log.shouldLog(Log.WARN))
+                            _log.debug("Huge metainfo size " + metaSize + " from: " + peer);
                         peer.disconnect(false);
                         return;
                     }
@@ -112,8 +129,7 @@ abstract class ExtensionHandler {
             }
         } catch (Exception e) {
             if (_log.shouldLog(Log.WARN))
-                _log.info("Handshake exception from " + peer, e);
-           //peer.disconnect(false);
+                _log.warn("Handshake exception from " + peer, e);
         }
     }
 
@@ -140,6 +156,8 @@ abstract class ExtensionHandler {
 
             MagnetState state = peer.getMagnetState();
             if (type == TYPE_REQUEST) {
+                if (_log.shouldLog(Log.DEBUG))
+                    _log.debug("Got request for " + piece + " from: " + peer);
                 byte[] pc;
                 synchronized(state) {
                     pc = state.getChunk(piece);
@@ -150,12 +168,19 @@ abstract class ExtensionHandler {
                 listener.uploaded(peer, pc.length);
             } else if (type == TYPE_DATA) {
                 int size = map.get("total_size").getInt();
+                if (_log.shouldLog(Log.DEBUG))
+                    _log.debug("Got data for " + piece + " length " + size + " from: " + peer);
                 boolean done;
                 int chk = -1;
                 synchronized(state) {
                     if (state.isComplete())
                         return;
                     int len = is.available();
+                    if (len != size) {
+                        // probably fatal
+                        if (_log.shouldLog(Log.WARN))
+                            _log.warn("total_size " + size + " but avail data " + len);
+                    }
                     peer.downloaded(len);
                     listener.downloaded(peer, len);
                     done = state.saveChunk(piece, bs, bs.length - len, len);
@@ -219,15 +244,15 @@ abstract class ExtensionHandler {
 
     private static void sendPiece(Peer peer, int piece, byte[] data) {
         Map<String, Object> map = new HashMap();
-        map.put("msg_type", Integer.valueOf(TYPE_REQUEST));
+        map.put("msg_type", Integer.valueOf(TYPE_DATA));
         map.put("piece", Integer.valueOf(piece));
         map.put("total_size", Integer.valueOf(data.length));
         byte[] dict = BEncoder.bencode(map);
         byte[] payload = new byte[dict.length + data.length];
         System.arraycopy(dict, 0, payload, 0, dict.length);
-        System.arraycopy(data, 0, payload, dict.length, payload.length);
+        System.arraycopy(data, 0, payload, dict.length, data.length);
         try {
-            int hisMsgCode = peer.getHandshakeMap().get("m").getMap().get("METADATA").getInt();
+            int hisMsgCode = peer.getHandshakeMap().get("m").getMap().get(TYPE_METADATA).getInt();
             peer.sendExtension(hisMsgCode, payload);
         } catch (Exception e) {
             // NPE, no metadata caps
diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
index ef194ac3f4516437cf17a516eff75e28b23d5768..775d4cc4ab7e5b6d902ab9c25f6d48099ce02a83 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
@@ -446,6 +446,21 @@ public class I2PSnarkUtil {
         return Boolean.valueOf(rv).booleanValue();
     }
 
+    /**
+     *  Like DataHelper.toHexString but ensures no loss of leading zero bytes
+     *  @since 0.8.4
+     */
+    public static String toHex(byte[] b) {
+        StringBuilder buf = new StringBuilder(40);
+        for (int i = 0; i < b.length; i++) {
+            int bi = b[i] & 0xff;
+            if (bi < 16)
+                buf.append('0');
+            buf.append(Integer.toHexString(bi));
+        }
+        return buf.toString();
+    }
+
     /** hook between snark's logger and an i2p log */
     void debug(String msg, int snarkDebugLevel) {
         debug(msg, snarkDebugLevel, null);
diff --git a/apps/i2psnark/java/src/org/klomp/snark/MagnetState.java b/apps/i2psnark/java/src/org/klomp/snark/MagnetState.java
index fb7e5a542da7cd8a4f861841083877bc6252d372..e9eb5638963a309ccf12c123341bd1abee19c5e1 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/MagnetState.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/MagnetState.java
@@ -53,8 +53,6 @@ class MagnetState {
             metainfo = meta;
             initialize(meta.getInfoBytes().length);
             complete = true;
-        } else {
-            metainfoBytes = new byte[metaSize];
         }
     }
 
@@ -68,10 +66,13 @@ class MagnetState {
         isInitialized = true;
         metaSize = size;
         totalChunks = (size + (CHUNK_SIZE - 1)) / CHUNK_SIZE;
-        if (metainfo == null) {
+        if (metainfo != null) {
+            metainfoBytes = metainfo.getInfoBytes();
+        } else {
             // we don't need these if complete
             have = new BitField(totalChunks);
             requested = new BitField(totalChunks);
+            metainfoBytes = new byte[metaSize];
         }
     }
 
@@ -194,8 +195,7 @@ class MagnetState {
         InputStream is = new ByteArrayInputStream(metainfoBytes);
         BDecoder dec = new BDecoder(is);
         BEValue bev = dec.bdecodeMap();
-        Map<String, BEValue> info = bev.getMap();
-        map.put("info", info);
+        map.put("info", bev);
         MetaInfo newmeta = new MetaInfo(map);
         if (!DataHelper.eq(newmeta.getInfoHash(), infohash))
             throw new IOException("info hash mismatch");
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Message.java b/apps/i2psnark/java/src/org/klomp/snark/Message.java
index 57fecf43d97b982b7302f3ee50ca5edab4018560..01c474216e4f086226fd6142dba7ebbc183f25ba 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Message.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Message.java
@@ -104,7 +104,7 @@ class Message
     if (type == REQUEST || type == CANCEL)
       datalen += 4;
 
-    // length is 1 byte
+    // msg type is 1 byte
     if (type == EXTENSION)
       datalen += 1;
 
@@ -167,6 +167,8 @@ class Message
         return "PIECE(" + piece + "," + begin + "," + length + ")";
       case CANCEL:
         return "CANCEL(" + piece + "," + begin + "," + length + ")";
+      case PORT:
+        return "PORT(" + piece + ")";
       case EXTENSION:
         return "EXTENSION(" + piece + ',' + data.length + ')';
       default:
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java
index de8d2b1d09161f066cd49f2fdfe06fe103dd8a8f..18c9ad229face87ee5d1cec7e1d32bf6f1c99a0a 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java
@@ -120,7 +120,7 @@ public class Peer implements Comparable
     this.peerID = new PeerID(id, sock.getPeerDestination());
     _id = ++__id;
     if (_log.shouldLog(Log.DEBUG))
-        _log.debug("Creating a new peer with " + peerID.toString(), new Exception("creating " + _id));
+        _log.debug("Creating a new peer " + peerID.toString(), new Exception("creating " + _id));
   }
 
   /**
@@ -261,14 +261,22 @@ public class Peer implements Comparable
                 _log.debug("Already have din [" + sock + "] with " + toString());
           }
         
+        // bad idea?
+        if (metainfo == null && (options & OPTION_EXTENSION) == 0) {
+            if (_log.shouldLog(Log.INFO))
+                _log.info("Peer does not support extensions and we need metainfo, dropping");
+            throw new IOException("Peer does not support extensions and we need metainfo, dropping");
+        }
+
         PeerConnectionIn in = new PeerConnectionIn(this, din);
         PeerConnectionOut out = new PeerConnectionOut(this, dout);
         PeerState s = new PeerState(this, listener, metainfo, in, out);
         
         if ((options & OPTION_EXTENSION) != 0) {
             if (_log.shouldLog(Log.DEBUG))
-                _log.debug("Peer supports extensions, sending test message");
-            out.sendExtension(0, ExtensionHandler.getHandshake());
+                _log.debug("Peer supports extensions, sending reply message");
+            int metasize = metainfo != null ? metainfo.getInfoBytes().length : -1;
+            out.sendExtension(0, ExtensionHandler.getHandshake(metasize));
         }
 
         if ((options & OPTION_DHT) != 0 && util.getDHT() != null) {
@@ -423,6 +431,7 @@ public class Peer implements Comparable
    *  @since 0.8.4
    */
   public void setMetaInfo(MetaInfo meta) {
+    metainfo = meta;
     PeerState s = state;
     if (s != null)
         s.setMetaInfo(meta);
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
index a27fc1c9dd17a91cfcc6698349b7e0186c9af3be..33da75263e9f20e69e3c9f2d3f932e9b9354b6c1 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
@@ -32,6 +32,13 @@ class PeerConnectionIn implements Runnable
   private final Peer peer;
   private final DataInputStream din;
 
+  // The max length of a complete message in bytes.
+  // The biggest is the piece message, for which the length is the
+  // request size (32K) plus 9. (we could also check if Storage.MAX_PIECES / 8
+  // in the bitfield message is bigger but it's currently 5000/8 = 625 so don't bother)
+  private static final int MAX_MSG_SIZE = Math.max(PeerState.PARTSIZE + 9,
+                                                   MagnetState.CHUNK_SIZE + 100);  // 100 for the ext msg dictionary
+
   private Thread thread;
   private volatile boolean quit;
 
@@ -77,13 +84,9 @@ class PeerConnectionIn implements Runnable
             int len;
         
             // Wait till we hear something...
-            // The length of a complete message in bytes.
-            // The biggest is the piece message, for which the length is the
-            // request size (32K) plus 9. (we could also check if Storage.MAX_PIECES / 8
-            // in the bitfield message is bigger but it's currently 5000/8 = 625 so don't bother)
             int i = din.readInt();
             lastRcvd = System.currentTimeMillis();
-            if (i < 0 || i > PeerState.PARTSIZE + 9)
+            if (i < 0 || i > MAX_MSG_SIZE)
               throw new IOException("Unexpected length prefix: " + i);
 
             if (i == 0)
@@ -176,13 +179,14 @@ class PeerConnectionIn implements Runnable
                 ps.portMessage(port);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received port message from " + peer);
+                break;
               case 20:  // Extension message
                 int id = din.readUnsignedByte();
                 byte[] payload = new byte[i-2];
                 din.readFully(payload);
-                ps.extensionMessage(id, payload);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received extension message from " + peer);
+                ps.extensionMessage(id, payload);
                 break;
               default:
                 byte[] bs = new byte[i-1];
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
index 504749b6255d9aae9d5ae11fbce49aa903507ca2..74a840fc7b4e132a6e622908babfd48fe36f87c1 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
@@ -410,7 +410,7 @@ public class PeerCoordinator implements PeerListener
                     name = "Magnet";
                 else
                     name = metainfo.getName();
-               _log.info("New connection to peer: " + peer + " for " + metainfo.getName());
+               _log.info("New connection to peer: " + peer + " for " + name);
             }
 
             // Add it to the beginning of the list.
@@ -1169,9 +1169,6 @@ public class PeerCoordinator implements PeerListener
                       _log.warn("Got completed metainfo via extension");
                   metainfo = magnetState.getMetaInfo();
                   listener.gotMetaInfo(this, metainfo);
-                  for (Peer p : peers) {
-                      p.setMetaInfo(metainfo);
-                  }
               }
           }
       }
@@ -1184,6 +1181,11 @@ public class PeerCoordinator implements PeerListener
    */
   public void setStorage(Storage stg) {
       storage = stg;
+      setWantedPieces();
+      // ok we should be in business
+      for (Peer p : peers) {
+          p.setMetaInfo(metainfo);
+      }
   }
 
   /**
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerState.java b/apps/i2psnark/java/src/org/klomp/snark/PeerState.java
index 4164e310a21936c38792407a8636e0f2c4fc4af3..6989f1ea65caa8a97d0ac0ad5ae613f53337edfe 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerState.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerState.java
@@ -511,6 +511,8 @@ class PeerState implements DataLoader
           //bitfield = new BitField(meta.getPieces());
       }
       metainfo = meta;
+      if (bitfield.count() > 0)
+          setInteresting(true);
   }
 
   /** @since 0.8.4 */
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
index 45231965fcc493396a939f03c1d685b21f604c60..d114fe0ee2ea01c8e23479470731ab679150029a 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
@@ -243,7 +243,7 @@ public class Snark
   public static final String PROP_MAX_CONNECTIONS = "i2psnark.maxConnections";
 
   /** most of these used to be public, use accessors below instead */
-  private final String torrent;
+  private String torrent;
   private MetaInfo meta;
   private Storage storage;
   private PeerCoordinator coordinator;
@@ -360,6 +360,7 @@ public class Snark
             }
           }
         meta = new MetaInfo(new BDecoder(in));
+        infoHash = meta.getInfoHash();
       }
     catch(IOException ioe)
       {
@@ -1028,8 +1029,13 @@ public class Snark
       meta = metainfo;
       try {
           storage = new Storage(_util, meta, this);
-          if (completeListener != null)
-              completeListener.gotMetaInfo(this);
+          storage.check(rootDataDir);
+          if (completeListener != null) {
+              String newName = completeListener.gotMetaInfo(this);
+              if (newName != null)
+                  torrent = newName;
+              // else some horrible problem
+          }
           coordinator.setStorage(storage);
       } catch (IOException ioe) {
           if (storage != null) {
@@ -1125,9 +1131,10 @@ public class Snark
      * metainfo and storage. The listener should now call getMetaInfo()
      * and save the data to disk.
      *
+     * @return the new name for the torrent or null on error
      * @since 0.8.4
      */
-    public void gotMetaInfo(Snark snark);
+    public String gotMetaInfo(Snark snark);
 
     // not really listeners but the easiest way to get back to an optional SnarkManager
     public long getSavedTorrentTime(Snark snark);
diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
index 27640d53baacf2442e310f79fb5cdcc06cbfe8e6..1e5ba10bf25d5b01655aeb306f33a455e5f417b2 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
@@ -68,7 +68,7 @@ public class SnarkManager implements Snark.CompleteListener {
     public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
     public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
     public static final String PROP_META_PRIORITY_SUFFIX = ".priority";
-    public static final String PROP_META_MAGNET_SUFFIX = ".magnet";
+    public static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
 
     private static final String CONFIG_FILE = "i2psnark.config";
     public static final String PROP_AUTO_START = "i2snark.autoStart";   // oops
@@ -340,7 +340,9 @@ public class SnarkManager implements Snark.CompleteListener {
             int oldI2CPPort = _util.getI2CPPort();
             String oldI2CPHost = _util.getI2CPHost();
             int port = oldI2CPPort;
-            try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
+            if (i2cpPort != null) {
+                try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
+            }
             String host = oldI2CPHost;
             Map opts = new HashMap();
             if (i2cpOpts == null) i2cpOpts = "";
@@ -627,7 +629,7 @@ public class SnarkManager implements Snark.CompleteListener {
      * @throws RuntimeException via Snark.fatal()
      * @since 0.8.4
      */
-    public void addMagnet(String name, byte[] ih) {
+    public void addMagnet(String name, byte[] ih, boolean updateStatus) {
         Snark torrent = new Snark(_util, name, ih, this,
                                   _peerCoordinatorSet, _connectionAcceptor,
                                   false, getDataDir().getPath());
@@ -640,6 +642,8 @@ public class SnarkManager implements Snark.CompleteListener {
             }
             // Tell the dir monitor not to delete us
             _magnets.add(name);
+            if (updateStatus)
+                saveMagnetStatus(ih);
             _snarks.put(name, torrent);
         }
         if (shouldAutoStart()) {
@@ -667,6 +671,7 @@ public class SnarkManager implements Snark.CompleteListener {
         }
         snark.stopTorrent();
         _magnets.remove(snark.getName());
+        removeMagnetStatus(snark.getInfoHash());
     }
 
     /**
@@ -914,6 +919,28 @@ public class SnarkManager implements Snark.CompleteListener {
         saveConfig();
     }
     
+    /**
+     *  Just remember we have it
+     *  @since 0.8.4
+     */
+    public void saveMagnetStatus(byte[] ih) {
+        String infohash = Base64.encode(ih);
+        infohash = infohash.replace('=', '$');
+        _config.setProperty(PROP_META_MAGNET_PREFIX + infohash, ".");
+        saveConfig();
+    }
+    
+    /**
+     *  Remove the magnet marker from the config file.
+     *  @since 0.8.4
+     */
+    public void removeMagnetStatus(byte[] ih) {
+        String infohash = Base64.encode(ih);
+        infohash = infohash.replace('=', '$');
+        _config.remove(PROP_META_MAGNET_PREFIX + infohash);
+        saveConfig();
+    }
+    
     /**
      *  Does not really delete on failure, that's the caller's responsibility.
      *  Warning - does not validate announce URL - use TrackerClient.isValidAnnounce()
@@ -1032,12 +1059,10 @@ public class SnarkManager implements Snark.CompleteListener {
                 }
             }
 
-//start magnets
-
-
             // here because we need to delay until I2CP is up
             // although the user will see the default until then
             getBWLimit();
+            boolean doMagnets = true;
             while (true) {
                 File dir = getDataDir();
                 if (_log.shouldLog(Log.DEBUG))
@@ -1050,6 +1075,10 @@ public class SnarkManager implements Snark.CompleteListener {
                 } catch (Exception e) {
                     _log.error("Error in the DirectoryMonitor", e);
                 }
+                if (doMagnets) {
+                    addMagnets();
+                    doMagnets = false;
+                }
                 try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
             }
         }
@@ -1090,9 +1119,10 @@ public class SnarkManager implements Snark.CompleteListener {
      * and save the data to disk.
      * A Snark.CompleteListener method.
      *
+     * @return the new name for the torrent or null on error
      * @since 0.8.4
      */
-    public void gotMetaInfo(Snark snark) {
+    public String gotMetaInfo(Snark snark) {
         MetaInfo meta = snark.getMetaInfo();
         Storage storage = snark.getStorage();
         if (meta != null && storage != null) {
@@ -1100,23 +1130,51 @@ public class SnarkManager implements Snark.CompleteListener {
             if (rejectMessage != null) {
                 addMessage(rejectMessage);
                 snark.stopTorrent();
-                return;
+                return null;
             }
             saveTorrentStatus(meta, storage.getBitField(), null); // no file priorities
             String name = (new File(getDataDir(), storage.getBaseName() + ".torrent")).getAbsolutePath();
             try {
                 synchronized (_snarks) {
                     locked_writeMetaInfo(meta, name);
+                    // put it in the list under the new name
+                    _snarks.remove(snark.getName());
+                    _snarks.put(name, snark);
                 }
+                _magnets.remove(snark.getName());
+                removeMagnetStatus(snark.getInfoHash());
+                addMessage(_("Metainfo received for {0}", snark.getName()));
+                addMessage(_("Starting up torrent {0}", storage.getBaseName()));
+                return name;
             } catch (IOException ioe) {
                 addMessage(_("Failed to copy torrent file to {0}", name));
                 _log.error("Failed to write torrent file", ioe);
             }
         }
+        return null;
     }
 
     // End Snark.CompleteListeners
 
+    /**
+     * Add all magnets from the config file
+     * @since 0.8.4
+     */
+    private void addMagnets() {
+        for (Object o : _config.keySet()) {
+            String k = (String) o;
+            if (k.startsWith(PROP_META_MAGNET_PREFIX)) {
+                String b64 = k.substring(PROP_META_MAGNET_PREFIX.length());
+                b64 = b64.replace('$', '=');
+                byte[] ih = Base64.decode(b64);
+                // ignore value
+                if (ih != null && ih.length == 20)
+                    addMagnet("Magnet: " + I2PSnarkUtil.toHex(ih), ih, false);
+                // else remove from config?
+            }
+        }
+    }
+
     private void monitorTorrents(File dir) {
         String fileNames[] = dir.list(TorrentFilenameFilter.instance());
         List<String> foundNames = new ArrayList(0);
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
index 719edee69bc2538c5ff5ae908cb26912b8acb1bd..d03753d6bd396290eeb65ded028ff3a89327424c 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
@@ -173,7 +173,8 @@ public class TrackerClient extends I2PAppThread
                 continue;
              if (primary.startsWith("http://i2p/" + dest))
                 continue;
-             trackers.add(new Tracker(url, false));
+             // opentrackers are primary if we don't have primary
+             trackers.add(new Tracker(url, primary.equals("")));
              _log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
         }
     }
@@ -238,7 +239,7 @@ public class TrackerClient extends I2PAppThread
             
             uploaded = coordinator.getUploaded();
             downloaded = coordinator.getDownloaded();
-            left = coordinator.getLeft();
+            left = coordinator.getLeft();   // -1 in magnet mode
             
             // First time we got a complete download?
             String event;
@@ -289,7 +290,7 @@ public class TrackerClient extends I2PAppThread
                         }
                     }
 
-                    if ( (left > 0) && (!completed) ) {
+                    if ( (left != 0) && (!completed) ) {
                         // we only want to talk to new people if we need things
                         // from them (duh)
                         List<Peer> ordered = new ArrayList(peers);
@@ -344,7 +345,7 @@ public class TrackerClient extends I2PAppThread
             // FIXME this needs to be in its own thread
             if (_util.getDHT() != null && !stop) {
                 int numwant;
-                if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
+                if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
                     numwant = 1;
                 else
                     numwant = _util.getMaxConnections();
@@ -362,7 +363,7 @@ public class TrackerClient extends I2PAppThread
                     List<Peer> peers = new ArrayList(hashes.size());
                     for (Hash h : hashes) {
                         PeerID pID = new PeerID(h.getData());
-                        peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), meta));
+                        peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
                     }
                     Collections.shuffle(peers, r);
                     Iterator<Peer> it = peers.iterator();
@@ -370,7 +371,7 @@ public class TrackerClient extends I2PAppThread
                         Peer cur = it.next();
                         if (coordinator.addPeer(cur)) {
                             int delay = DELAY_MUL;
-                            delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
+                            delay *= r.nextInt(10);
                             delay += DELAY_MIN;
                             try { Thread.sleep(delay); } catch (InterruptedException ie) {}
                          }
@@ -418,6 +419,8 @@ public class TrackerClient extends I2PAppThread
                                 long downloaded, long left, String event)
     throws IOException
   {
+    // What do we send for left in magnet mode? Can we omit it?
+    long tleft = left >= 0 ? left : 1;
     String s = tr.announce
       + "?info_hash=" + infoHash
       + "&peer_id=" + peerID
@@ -425,10 +428,10 @@ public class TrackerClient extends I2PAppThread
       + "&ip=" + _util.getOurIPString() + ".i2p"
       + "&uploaded=" + uploaded
       + "&downloaded=" + downloaded
-      + "&left=" + left
+      + "&left=" + tleft
       + "&compact=1"   // NOTE: opentracker will return 400 for &compact alone
       + ((! event.equals(NO_EVENT)) ? ("&event=" + event) : "");
-    if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
+    if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
         s += "&numwant=0";
     else
         s += "&numwant=" + _util.getMaxConnections();
@@ -445,7 +448,7 @@ public class TrackerClient extends I2PAppThread
         in = new FileInputStream(fetched);
 
         TrackerInfo info = new TrackerInfo(in, snark.getID(),
-                                           snark.getMetaInfo());
+                                           snark.getInfoHash(), snark.getMetaInfo());
         _util.debug("TrackerClient response: " + info, Snark.INFO);
 
         String failure = info.getFailureReason();
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
index ef67fc58b1195fbbb03347ac92cf75c568f8b916..1b829d0eef00d674fdbbf5225381014c931beb8d 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
@@ -46,19 +46,20 @@ public class TrackerInfo
   private int complete;
   private int incomplete;
 
-  public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo)
+  /** @param metainfo may be null */
+  public TrackerInfo(InputStream in, byte[] my_id, byte[] infohash, MetaInfo metainfo)
     throws IOException
   {
-    this(new BDecoder(in), my_id, metainfo);
+    this(new BDecoder(in), my_id, infohash, metainfo);
   }
 
-  public TrackerInfo(BDecoder be, byte[] my_id, MetaInfo metainfo)
+  private TrackerInfo(BDecoder be, byte[] my_id, byte[] infohash, MetaInfo metainfo)
     throws IOException
   {
-    this(be.bdecodeMap().getMap(), my_id, metainfo);
+    this(be.bdecodeMap().getMap(), my_id, infohash, metainfo);
   }
 
-  public TrackerInfo(Map m, byte[] my_id, MetaInfo metainfo)
+  private TrackerInfo(Map m, byte[] my_id, byte[] infohash, MetaInfo metainfo)
     throws IOException
   {
     BEValue reason = (BEValue)m.get("failure reason");
@@ -84,10 +85,10 @@ public class TrackerInfo
             Set<Peer> p;
             try {
               // One big string (the official compact format)
-              p = getPeers(bePeers.getBytes(), my_id, metainfo);
+              p = getPeers(bePeers.getBytes(), my_id, infohash, metainfo);
             } catch (InvalidBEncodingException ibe) {
               // List of Dictionaries or List of Strings
-              p = getPeers(bePeers.getList(), my_id, metainfo);
+              p = getPeers(bePeers.getList(), my_id, infohash, metainfo);
             }
             peers = p;
         }
@@ -123,7 +124,7 @@ public class TrackerInfo
 ******/
 
   /** List of Dictionaries or List of Strings */
-  private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, MetaInfo metainfo)
+  private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, byte[] infohash, MetaInfo metainfo)
     throws IOException
   {
     Set<Peer> peers = new HashSet(l.size());
@@ -144,7 +145,7 @@ public class TrackerInfo
                 continue;
             }
         }
-        peers.add(new Peer(peerID, my_id, metainfo.getInfoHash(), metainfo));
+        peers.add(new Peer(peerID, my_id, infohash, metainfo));
       }
 
     return peers;
@@ -156,7 +157,7 @@ public class TrackerInfo
    *  One big string of concatenated 32-byte hashes
    *  @since 0.8.1
    */
-  private static Set<Peer> getPeers(byte[] l, byte[] my_id, MetaInfo metainfo)
+  private static Set<Peer> getPeers(byte[] l, byte[] my_id, byte[] infohash, MetaInfo metainfo)
     throws IOException
   {
     int count = l.length / HASH_LENGTH;
@@ -172,7 +173,7 @@ public class TrackerInfo
             // won't happen
             continue;
         }
-        peers.add(new Peer(peerID, my_id, metainfo.getInfoHash(), metainfo));
+        peers.add(new Peer(peerID, my_id, infohash, metainfo));
       }
 
     return peers;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
index 986e456437a7e57770e442d207f12b3377019c13..75a63bab19892c80d3b8afcc46e2f237d76c05a0 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
@@ -24,6 +24,8 @@ import java.io.UnsupportedEncodingException;
 import java.util.List;
 import java.util.Map;
 
+import net.i2p.data.Base64;
+
 /**
  * Holds different types that a bencoded byte array can represent.
  * You need to call the correct get method to get the correct java
@@ -180,10 +182,14 @@ public class BEValue
       {
         byte[] bs = (byte[])value;
         // XXX - Stupid heuristic... and not UTF-8
-        if (bs.length <= 12)
-          valueString = new String(bs);
+        //if (bs.length <= 12)
+        //  valueString = new String(bs);
+        //else
+        //  valueString = "bytes:" + bs.length;
+        if (bs.length <= 32)
+          valueString =  bs.length + " bytes: " + Base64.encode(bs);
         else
-          valueString = "bytes:" + bs.length;
+          valueString =  bs.length + " bytes";
       }
     else
       valueString = value.toString();
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 bd08dc46d3a853a572dbefa709e379ba28c090ac..077b24351f25e6857de82d5d8bc6ff6fb36a6621 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -1314,15 +1314,17 @@ public class I2PSnarkServlet extends Default {
         out.write("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
         out.write(renderOptions(0, 4, options.remove("outbound.length"), "outbound.length", HOP));
 
-        out.write("<tr><td>");
-        out.write(_("I2CP host"));
-        out.write(": <td><input type=\"text\" name=\"i2cpHost\" value=\"" 
-                  + _manager.util().getI2CPHost() + "\" size=\"15\" > ");
-
-        out.write("<tr><td>");
-        out.write(_("I2CP port"));
-        out.write(": <td><input type=\"text\" name=\"i2cpPort\" class=\"r\" value=\"" +
-                  + _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" > <br>\n");
+        if (!_context.isRouterContext()) {
+            out.write("<tr><td>");
+            out.write(_("I2CP host"));
+            out.write(": <td><input type=\"text\" name=\"i2cpHost\" value=\"" 
+                      + _manager.util().getI2CPHost() + "\" size=\"15\" > ");
+
+            out.write("<tr><td>");
+            out.write(_("I2CP port"));
+            out.write(": <td><input type=\"text\" name=\"i2cpPort\" class=\"r\" value=\"" +
+                      + _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" > <br>\n");
+        }
 
         StringBuilder opts = new StringBuilder(64);
         for (Iterator iter = options.entrySet().iterator(); iter.hasNext(); ) {