diff --git a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
index 2f01fe62c2abe95437bd8173155f69cc967b4101..3fc4977f6e92ee74420099b46d9b5ed6ff37a57d 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
@@ -54,28 +54,30 @@ public class MetaInfo
   private final byte[] info_hash;
   private final String name;
   private final String name_utf8;
-  private final List files;
-  private final List files_utf8;
-  private final List lengths;
+  private final List<List<String>> files;
+  private final List<List<String>> files_utf8;
+  private final List<Long> lengths;
   private final int piece_length;
   private final byte[] piece_hashes;
   private final long length;
-  private Map infoMap;
+  private Map<String, BEValue> infoMap;
 
   /**
    *  Called by Storage when creating a new torrent from local data
    *
    *  @param announce may be null
+   *  @param files null for single-file torrent
+   *  @param lengths null for single-file torrent
    */
-  MetaInfo(String announce, String name, String name_utf8, List files, List lengths,
+  MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
            int piece_length, byte[] piece_hashes, long length)
   {
     this.announce = announce;
     this.name = name;
     this.name_utf8 = name_utf8;
-    this.files = files;
+    this.files = files == null ? null : Collections.unmodifiableList(files);
     this.files_utf8 = null;
-    this.lengths = lengths;
+    this.lengths = lengths == null ? null : Collections.unmodifiableList(lengths);
     this.piece_length = piece_length;
     this.piece_hashes = piece_hashes;
     this.length = length;
@@ -131,7 +133,7 @@ public class MetaInfo
     if (val == null)
         throw new InvalidBEncodingException("Missing info map");
     Map info = val.getMap();
-    infoMap = info;
+    infoMap = Collections.unmodifiableMap(info);
 
     val = (BEValue)info.get("name");
     if (val == null)
@@ -171,39 +173,39 @@ public class MetaInfo
           throw new InvalidBEncodingException
             ("Missing length number and/or files list");
 
-        List list = val.getList();
+        List<BEValue> list = val.getList();
         int size = list.size();
         if (size == 0)
           throw new InvalidBEncodingException("zero size files list");
 
-        files = new ArrayList(size);
-        files_utf8 = new ArrayList(size);
-        lengths = new ArrayList(size);
+        List<List<String>> m_files = new ArrayList(size);
+        List<List<String>> m_files_utf8 = new ArrayList(size);
+        List<Long> m_lengths = new ArrayList(size);
         long l = 0;
         for (int i = 0; i < list.size(); i++)
           {
-            Map desc = ((BEValue)list.get(i)).getMap();
-            val = (BEValue)desc.get("length");
+            Map<String, BEValue> desc = list.get(i).getMap();
+            val = desc.get("length");
             if (val == null)
               throw new InvalidBEncodingException("Missing length number");
             long len = val.getLong();
-            lengths.add(new Long(len));
+            m_lengths.add(new Long(len));
             l += len;
 
             val = (BEValue)desc.get("path");
             if (val == null)
               throw new InvalidBEncodingException("Missing path list");
-            List path_list = val.getList();
+            List<BEValue> path_list = val.getList();
             int path_length = path_list.size();
             if (path_length == 0)
               throw new InvalidBEncodingException("zero size file path list");
 
-            List file = new ArrayList(path_length);
-            Iterator it = path_list.iterator();
+            List<String> file = new ArrayList(path_length);
+            Iterator<BEValue> it = path_list.iterator();
             while (it.hasNext())
-              file.add(((BEValue)it.next()).getString());
+              file.add(it.next().getString());
 
-            files.add(file);
+            m_files.add(Collections.unmodifiableList(file));
             
             val = (BEValue)desc.get("path.utf-8");
             if (val != null) {
@@ -213,11 +215,14 @@ public class MetaInfo
                     file = new ArrayList(path_length);
                     it = path_list.iterator();
                     while (it.hasNext())
-                        file.add(((BEValue)it.next()).getString());
-                    files_utf8.add(file);
+                        file.add(it.next().getString());
+                    m_files_utf8.add(Collections.unmodifiableList(file));
                 }
             }
           }
+        files = Collections.unmodifiableList(m_files);
+        files_utf8 = Collections.unmodifiableList(m_files_utf8);
+        lengths = Collections.unmodifiableList(m_lengths);
         length = l;
       }
 
@@ -265,9 +270,8 @@ public class MetaInfo
    * a single name. It has the same size as the list returned by
    * getLengths().
    */
-  public List getFiles()
+  public List<List<String>> getFiles()
   {
-    // XXX - Immutable?
     return files;
   }
 
@@ -276,9 +280,8 @@ public class MetaInfo
    * files, or null if it is a single file. It has the same size as
    * the list returned by getFiles().
    */
-  public List getLengths()
+  public List<Long> getLengths()
   {
-    // XXX - Immutable?
     return lengths;
   }
 
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java
index 0f13d1470d49787b9d1fcf6950d8027dd5516c07..b31ba65e2f315ff3b46fc8935bd16b109955f78b 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java
@@ -32,6 +32,7 @@ import java.util.Map;
 
 import net.i2p.client.streaming.I2PSocket;
 import net.i2p.data.DataHelper;
+import net.i2p.data.Destination;
 import net.i2p.util.Log;
 
 import org.klomp.snark.bencode.BEValue;
@@ -384,6 +385,13 @@ public class Peer implements Comparable
       return options;
   }
 
+  /** @since 0.8.4 */
+  public Destination getDestination() {
+      if (sock == null)
+          return null;
+      return sock.getPeerDestination();
+  }
+
   /**
    *  Shared state across all peers, callers must sync on returned object
    *  @return non-null
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
index 65a869845322de0bfdb4a9a5e482cd51651fa1cb..6370eaca646983d0729fceb3f97aea0a7c889d7f 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
@@ -37,7 +37,8 @@ class PeerCheckerTask extends TimerTask
   private static final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
 
   private final PeerCoordinator coordinator;
-  public I2PSnarkUtil _util;
+  private final I2PSnarkUtil _util;
+  private int _runCount;
 
   PeerCheckerTask(I2PSnarkUtil util, PeerCoordinator coordinator)
   {
@@ -49,6 +50,7 @@ class PeerCheckerTask extends TimerTask
 
   public void run()
   {
+        _runCount++;
         List<Peer> peerList = coordinator.peerList();
         if (peerList.isEmpty() || coordinator.halted()) {
           coordinator.setRateHistory(0, 0);
@@ -204,6 +206,10 @@ class PeerCheckerTask extends TimerTask
               }
             peer.retransmitRequests();
             peer.keepAlive();
+            // announce them to local tracker (TrackerClient does this too)
+            if (_util.getDHT() != null && (_runCount % 5) == 0) {
+                _util.getDHT().announce(coordinator.getInfoHash(), peer.getPeerID().getDestHash());
+            }
           }
 
         // Resync actual uploaders value
@@ -245,8 +251,13 @@ class PeerCheckerTask extends TimerTask
 
         // close out unused files, but we don't need to do it every time
         Storage storage = coordinator.getStorage();
-        if (storage != null && random.nextInt(4) == 0) {
+        if (storage != null && (_runCount % 4) == 0) {
                 storage.cleanRAFs();
         }
+
+        // announce ourselves to local tracker (TrackerClient does this too)
+        if (_util.getDHT() != null && (_runCount % 16) == 0) {
+            _util.getDHT().announce(coordinator.getInfoHash());
+        }
   }
 }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
index 0ad2c1e37261745101f07a8e7bca5677fb824308..504749b6255d9aae9d5ae11fbce49aa903507ca2 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
@@ -36,6 +36,8 @@ import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
 import net.i2p.util.SimpleTimer2;
 
+import org.klomp.snark.dht.KRPC;
+
 /**
  * Coordinates what peer does what.
  */
@@ -1186,10 +1188,13 @@ public class PeerCoordinator implements PeerListener
 
   /**
    *  PeerListener callback
+   *  Tell the DHT to ping it, this will get back the node info
    *  @since 0.8.4
    */
   public void gotPort(Peer peer, int port) {
-      // send to DHT
+      KRPC krpc = _util.getDHT();
+      if (krpc != null)
+          krpc.ping(peer.getDestination(), port);
   }
 
   /** Return number of allowed uploaders for this torrent.
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
index bd3d0d20dd29238da2afc621d75c0b425ddca592..45231965fcc493396a939f03c1d685b21f604c60 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
@@ -776,7 +776,7 @@ public class Snark
     }
 
     /**
-     *  @return needed of all torrent files, or total of metainfo file if fetching magnet, or -1
+     *  @return number of pieces still needed (magnet mode or not), or -1 if unknown
      *  @since 0.8.4
      */
     public long getNeeded() {
@@ -786,7 +786,7 @@ public class Snark
             // FIXME subtract chunks we have
             return meta.getTotalLength();
         // FIXME fake
-        return 16 * 16 * 1024;
+        return -1;
     }
 
     /**
@@ -800,6 +800,17 @@ public class Snark
         return 16*1024;
     }
 
+    /**
+     *  @return number of pieces
+     *  @since 0.8.4
+     */
+    public int getPieces() {
+        if (meta != null)
+            return meta.getPieces();
+        // FIXME else return metainfo pieces if available
+        return -1;
+    }
+
     /**
      *  @return true if restarted
      *  @since 0.8.4
diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
index c1410e7fa47fc76a876888ec05cab46e4741e368..27640d53baacf2442e310f79fb5cdcc06cbfe8e6 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
@@ -68,6 +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";
 
     private static final String CONFIG_FILE = "i2psnark.config";
     public static final String PROP_AUTO_START = "i2snark.autoStart";   // oops
@@ -1031,6 +1032,9 @@ 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();
@@ -1245,6 +1249,7 @@ public class SnarkManager implements Snark.CompleteListener {
                 if ( (snark != null) && (!snark.isStopped()) )
                     snark.stopTorrent();
             }
+//save magnets
         }
     }
 }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
index dcd78620d9704d3bdc17ac4ec6af9c2579316b12..77012de77dd57e34fd5be166e8888c017e924999 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
@@ -100,7 +100,7 @@ public class Storage
     getFiles(baseFile);
     
     long total = 0;
-    ArrayList lengthsList = new ArrayList();
+    ArrayList<Long> lengthsList = new ArrayList();
     for (int i = 0; i < lengths.length; i++)
       {
         long length = lengths[i];
@@ -122,10 +122,10 @@ public class Storage
     bitfield = new BitField(pieces);
     needed = 0;
 
-    List files = new ArrayList();
+    List<List<String>> files = new ArrayList();
     for (int i = 0; i < names.length; i++)
       {
-        List file = new ArrayList();
+        List<String> file = new ArrayList();
         StringTokenizer st = new StringTokenizer(names[i], File.separator);
         while (st.hasMoreTokens())
           {
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
index f437beeeb63666cab4b34b34da44f2cd75a23fcc..720a662b3a83a6bbdc8a99088be942fa5e05253f 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
@@ -277,18 +277,26 @@ public class TrackerClient extends I2PAppThread
                     runStarted = true;
                     tr.started = true;
 
-                    Set peers = info.getPeers();
+                    Set<Peer> peers = info.getPeers();
                     tr.seenPeers = info.getPeerCount();
                     if (snark.getTrackerSeenPeers() < tr.seenPeers) // update rising number quickly
                         snark.setTrackerSeenPeers(tr.seenPeers);
+
+                    // pass everybody over to our tracker
+                    if (_util.getDHT() != null) {
+                        for (Peer peer : peers) {
+                            _util.getDHT().announce(snark.getInfoHash(), peer.getPeerID().getDestHash());
+                        }
+                    }
+
                     if ( (left > 0) && (!completed) ) {
                         // we only want to talk to new people if we need things
                         // from them (duh)
-                        List ordered = new ArrayList(peers);
+                        List<Peer> ordered = new ArrayList(peers);
                         Collections.shuffle(ordered, r);
-                        Iterator it = ordered.iterator();
+                        Iterator<Peer> it = ordered.iterator();
                         while ((!stop) && it.hasNext()) {
-                          Peer cur = (Peer)it.next();
+                          Peer cur = it.next();
                           // FIXME if id == us || dest == us continue;
                           // only delay if we actually make an attempt to add peer
                           if(coordinator.addPeer(cur)) {
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 fefc808485ea99f698d4f953d4defb466e55a80e..61c9c8e572a3637c217125329e96f00f98b2e44c 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -6,6 +6,7 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.text.Collator;
+import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -875,8 +876,7 @@ public class I2PSnarkServlet extends Default {
         else if (isValid)
             icon = toIcon(meta.getName());
         else
-            // todo get a nice magnet icon?
-            icon = "page_white";
+            icon = "magnet";
         if (remaining == 0 || isMultiFile) {
             out.write(toImg(icon, _("Open")));
             out.write("</a>");
@@ -1375,6 +1375,7 @@ public class I2PSnarkServlet extends Default {
         if (ihash.length() == 32) {
             ih = Base32.decode(ihash);
         } else if (ihash.length() == 40) {
+            //  Like DataHelper.fromHexString() but ensures no loss of leading zero bytes
             ih = new byte[20];
             try {
                 for (int i = 0; i < 20; i++) {
@@ -1539,6 +1540,7 @@ public class I2PSnarkServlet extends Default {
 
         if (title.endsWith("/"))
             title = title.substring(0, title.length() - 1);
+        String directory = title;
         title = _("Torrent") + ": " + title;
         buf.append(title);
         buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" +
@@ -1550,10 +1552,40 @@ public class I2PSnarkServlet extends Default {
         boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete();
         if (showPriority)
             buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
-        buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" >" +
-            "<thead><tr><th>")
+        buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" ><thead>");
+        if (snark != null) {
+            // first row - torrent info
+            // FIXME center
+            buf.append("<tr><th colspan=\"" + (showPriority ? '4' : '3') + "\"><div>")
+                .append(_("Torrent")).append(": ").append(snark.getBaseName());
+            int pieces = snark.getPieces();
+            double completion = (pieces - snark.getNeeded()) / (double) pieces;
+            if (completion < 1.0)
+                buf.append("<br>").append(_("Completion")).append(": ").append((new DecimalFormat("0.00%")).format(completion));
+            else
+                buf.append("<br>").append(_("Complete"));
+            // else unknown
+            buf.append("<br>").append(_("Size")).append(": ").append(formatSize(snark.getTotalLength()));
+            MetaInfo meta = snark.getMetaInfo();
+            if (meta != null) {
+                List files = meta.getFiles();
+                int fileCount = files != null ? files.size() : 1;
+                buf.append("<br>").append(_("Files")).append(": ").append(fileCount);
+            }
+            buf.append("<br>").append(_("Pieces")).append(": ").append(pieces);
+            buf.append("<br>").append(_("Piece size")).append(": ").append(formatSize(snark.getPieceLength(0)));
+            String hex = toHex(snark.getInfoHash());
+            buf.append("<br>").append(_("Magnet link")).append(": <a href=\"").append(MAGNET).append(hex).append("\">")
+               .append(MAGNET).append(hex).append("</a>");
+            // We don't have the hash of the torrent file
+            //buf.append("<br>").append(_("Maggot link")).append(": <a href=\"").append(MAGGOT).append(hex).append(':').append(hex).append("\">")
+            //   .append(MAGGOT).append(hex).append(':').append(hex).append("</a>");
+            buf.append("</div></th></tr>");
+        }
+        // second row - dir info
+        buf.append("<tr><th>")
             .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;")
-            .append(title).append("</th><th align=\"right\">")
+            .append(_("Directory")).append(": ").append(directory).append("</th><th align=\"right\">")
             .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "size.png\" >&nbsp;")
             .append(_("Size"));
         buf.append("</th><th class=\"headerstatus\">")
@@ -1767,6 +1799,21 @@ public class I2PSnarkServlet extends Default {
         return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"/i2psnark/_icons/" + icon + ".png\">";
     }
 
+    /**
+     *  Like DataHelper.toHexString but ensures no loss of leading zero bytes
+     *  @since 0.8.4
+     */
+    private 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();
+    }
+
     /** @since 0.8.1 */
     private void savePriorities(Snark snark, Map postParams) {
         Storage storage = snark.getStorage();