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 a3d77a814a88df19d08969eafc906db5ca144d21..77207ec08acd336a002927092dff51d4e1bf50fd 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -857,29 +857,33 @@ public class I2PSnarkServlet extends Default {
         out.write("<td class=\"center " + rowClass + "\">");
         out.write(statusString + "</td>\n\t");
 
+        // (i) icon column
         out.write("<td class=\"" + rowClass + "\">");
+        if (isValid && meta.getAnnounce() != null) {
+            // Link to local details page - note that trailing slash on a single-file torrent
+            // gets us to the details page instead of the file.
+            //StringBuilder buf = new StringBuilder(128);
+            //buf.append("<a href=\"").append(snark.getBaseName())
+            //   .append("/\" title=\"").append(_("Torrent details"))
+            //   .append("\"><img alt=\"").append(_("Info")).append("\" border=\"0\" src=\"")
+            //   .append(_imgPath).append("details.png\"></a>");
+            //out.write(buf.toString());
+
+            // Link to tracker details page
+            String trackerLink = getTrackerLink(meta.getAnnounce(), snark.getInfoHash());
+            if (trackerLink != null)
+                out.write(trackerLink);
+        }
+
+        // File type icon column
+        out.write("</td>\n<td class=\"" + rowClass + "\">");
         if (isValid) {
+            // Link to local details page - note that trailing slash on a single-file torrent
+            // gets us to the details page instead of the file.
             StringBuilder buf = new StringBuilder(128);
             buf.append("<a href=\"").append(snark.getBaseName())
                .append("/\" title=\"").append(_("Torrent details"))
-               .append("\"><img alt=\"").append(_("Info")).append("\" border=\"0\" src=\"")
-               .append(_imgPath).append("details.png\"></a>");
-             out.write(buf.toString());
-        }
-
-        out.write("</td>\n<td class=\"" + rowClass + "\">");
-        StringBuilder buf = null;
-        if (remaining == 0 || isMultiFile) {
-            buf = new StringBuilder(128);
-            buf.append("<a href=\"").append(snark.getBaseName());
-            if (isMultiFile)
-                buf.append('/');
-            buf.append("\" title=\"");
-            if (isMultiFile)
-                buf.append(_("View files"));
-            else
-                buf.append(_("Open file"));
-            buf.append("\">");
+               .append("\">");
             out.write(buf.toString());
         }
         String icon;
@@ -889,15 +893,28 @@ public class I2PSnarkServlet extends Default {
             icon = toIcon(meta.getName());
         else
             icon = "magnet";
-        if (remaining == 0 || isMultiFile) {
-            out.write(toImg(icon, _("Open")));
+        if (isValid) {
+            out.write(toImg(icon, _("Info")));
             out.write("</a>");
         } else {
             out.write(toImg(icon));
         }
+
+        // Torrent name column
         out.write("</td><td class=\"snarkTorrentName " + rowClass + "\">");
-        if (remaining == 0 || isMultiFile)
+        if (remaining == 0 || isMultiFile) {
+            StringBuilder buf = new StringBuilder(128);
+            buf.append("<a href=\"").append(snark.getBaseName());
+            if (isMultiFile)
+                buf.append('/');
+            buf.append("\" title=\"");
+            if (isMultiFile)
+                buf.append(_("View files"));
+            else
+                buf.append(_("Open file"));
+            buf.append("\">");
             out.write(buf.toString());
+        }
         out.write(filename);
         if (remaining == 0 || isMultiFile)
             out.write("</a>");
@@ -1169,7 +1186,7 @@ public class I2PSnarkServlet extends Default {
         out.write(_("From URL"));
         out.write(":<td><input type=\"text\" name=\"newURL\" size=\"85\" value=\"" + newURL + "\"");
         out.write("title=\"");
-        out.write(_("Torrent file must originate from an I2P-based tracker"));
+        out.write(_("Enter the torrent file download URL (I2P only), magnet link, or maggot link"));
         out.write("\"> \n");
         // not supporting from file at the moment, since the file name passed isn't always absolute (so it may not resolve)
         //out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>");
diff --git a/core/java/build.xml b/core/java/build.xml
index 86e0039ac2f1917f73c60c0d9dea2c04e9d2296c..27ca00e6fe6dcf785957d1de77ea8f236d94bdb7 100644
--- a/core/java/build.xml
+++ b/core/java/build.xml
@@ -49,6 +49,7 @@
         <cobertura-instrument todir="./build/obj_test">
             <fileset dir="./build/obj">
                 <include name="**/*.class"/>
+                <exclude name="**/*Test.class" />
             </fileset>
         </cobertura-instrument>
     </target>
diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java
index 69b315a1a478be9ac84877478997734ab6d2e5b9..4e3db770b0674ac6785094181ed6bcee2592ba18 100644
--- a/core/java/src/net/i2p/data/DataHelper.java
+++ b/core/java/src/net/i2p/data/DataHelper.java
@@ -179,6 +179,7 @@ public class DataHelper {
      * @param props source
      * @return new offset
      */
+    @Deprecated
     public static int toProperties(byte target[], int offset, Properties props) throws DataFormatException, IOException {
         if (props != null) {
             OrderedProperties p = new OrderedProperties();
@@ -219,6 +220,7 @@ public class DataHelper {
      * @param target returned Properties
      * @return new offset
      */
+    @Deprecated
     public static int fromProperties(byte source[], int offset, Properties target) throws DataFormatException, IOException {
         int size = (int)fromLong(source, offset, 2);
         offset += 2;
@@ -254,6 +256,7 @@ public class DataHelper {
      *
      * @throws RuntimeException if either is too long.
      */
+    @Deprecated
     public static byte[] toProperties(Properties opts) {
         try {
             ByteArrayOutputStream baos = new ByteArrayOutputStream(2);
@@ -544,6 +547,7 @@ public class DataHelper {
     }
 
     /** @deprecated unused */
+    @Deprecated
     public static byte[] toDate(Date date) throws IllegalArgumentException {
         if (date == null)
             return toLong(DATE_LENGTH, 0L);
@@ -678,6 +682,7 @@ public class DataHelper {
      * @throws IOException if there is an IO error writing the boolean
      * @deprecated unused
      */
+    @Deprecated
     public static void writeBoolean(OutputStream out, Boolean bool) 
         throws DataFormatException, IOException {
         if (bool == null)
@@ -689,6 +694,7 @@ public class DataHelper {
     }
     
     /** @deprecated unused */
+    @Deprecated
     public static Boolean fromBoolean(byte data[], int offset) {
         if (data[offset] == BOOLEAN_TRUE)
             return Boolean.TRUE;
@@ -699,11 +705,13 @@ public class DataHelper {
     }
     
     /** @deprecated unused */
+    @Deprecated
     public static void toBoolean(byte data[], int offset, boolean value) {
         data[offset] = (value ? BOOLEAN_TRUE : BOOLEAN_FALSE);
     }
 
     /** @deprecated unused */
+    @Deprecated
     public static void toBoolean(byte data[], int offset, Boolean value) {
         if (value == null)
             data[offset] = BOOLEAN_UNKNOWN;
@@ -712,12 +720,16 @@ public class DataHelper {
     }
     
     /** deprecated - used only in DatabaseLookupMessage */
+    @Deprecated
     public static final byte BOOLEAN_TRUE = 0x1;
     /** deprecated - used only in DatabaseLookupMessage */
+    @Deprecated
     public static final byte BOOLEAN_FALSE = 0x0;
     /** @deprecated unused */
+    @Deprecated
     public static final byte BOOLEAN_UNKNOWN = 0x2;
     /** @deprecated unused */
+    @Deprecated
     public static final int BOOLEAN_LENGTH = 1;
 
     //
@@ -780,6 +792,7 @@ public class DataHelper {
      * Compare two integers, really just for consistency.
      * @deprecated inefficient
      */
+    @Deprecated
     public final static boolean eq(int lhs, int rhs) {
         return lhs == rhs;
     }
@@ -788,6 +801,7 @@ public class DataHelper {
      * Compare two longs, really just for consistency.
      * @deprecated inefficient
      */
+    @Deprecated
     public final static boolean eq(long lhs, long rhs) {
         return lhs == rhs;
     }
@@ -796,6 +810,7 @@ public class DataHelper {
      * Compare two bytes, really just for consistency.
      * @deprecated inefficient
      */
+    @Deprecated
     public final static boolean eq(byte lhs, byte rhs) {
         return lhs == rhs;
     }
@@ -974,6 +989,7 @@ public class DataHelper {
      * @return true if the line was read, false if eof was reached before a 
      *              newline was found
      */
+    @Deprecated
     public static boolean readLine(InputStream in, StringBuffer buf) throws IOException {
         return readLine(in, buf, null);
     }
@@ -987,6 +1003,7 @@ public class DataHelper {
      * Warning - 8KB line length limit as of 0.7.13, @throws IOException if exceeded
      * @deprecated use StringBuilder version
      */
+    @Deprecated
     public static boolean readLine(InputStream in, StringBuffer buf, Sha256Standalone hash) throws IOException {
         int c = -1;
         int i = 0;
diff --git a/core/java/src/net/i2p/data/DateAndFlags.java b/core/java/src/net/i2p/data/DateAndFlags.java
index 70f88ce655b27e4af5ca9bea42d624a5656f409f..0810cddca6e9db88719b7ace262f38803c0ca30b 100644
--- a/core/java/src/net/i2p/data/DateAndFlags.java
+++ b/core/java/src/net/i2p/data/DateAndFlags.java
@@ -36,7 +36,7 @@ public class DateAndFlags extends DataStructureImpl {
     /**
      *  @param flags 0 - 65535
      */
-    public DateAndFlags(int flags, long date) {
+    public DateAndFlags(long date, int flags) {
         _flags = flags;
         _date = date;
     }
@@ -44,7 +44,7 @@ public class DateAndFlags extends DataStructureImpl {
     /**
      *  @param flags 0 - 65535
      */
-    public DateAndFlags(int flags, Date date) {
+    public DateAndFlags(Date date, int flags) {
         _flags = flags;
         _date = date.getTime();
     }
diff --git a/core/java/src/net/i2p/util/Executor.java b/core/java/src/net/i2p/util/Executor.java
index 8092d7ac447ee98f152306101d59e925fb18baf6..3c81d46f157f581455d30c4ea19f52b2b6232e19 100644
--- a/core/java/src/net/i2p/util/Executor.java
+++ b/core/java/src/net/i2p/util/Executor.java
@@ -5,10 +5,10 @@ import java.util.List;
 import net.i2p.I2PAppContext;
 
 class Executor implements Runnable {
-    private I2PAppContext _context;
+    private final I2PAppContext _context;
     private Log _log;
     private final List _readyEvents;
-    private SimpleStore runn;
+    private final SimpleStore runn;
 
     public Executor(I2PAppContext ctx, Log log, List events, SimpleStore x) {
         _context = ctx;
@@ -31,9 +31,10 @@ class Executor implements Runnable {
                 try {
                     evt.timeReached();
                 } catch (Throwable t) {
-                    log("wtf, event borked: " + evt, t);
+                    log("Executing task " + evt + " exited unexpectedly, please report", t);
                 }
                 long time = _context.clock().now() - before;
+                // FIXME _log won't be non-null unless we already had a CRIT
                 if ( (time > 1000) && (_log != null) && (_log.shouldLog(Log.WARN)) )
                     _log.warn("wtf, event execution took " + time + ": " + evt);
             }
diff --git a/core/java/src/net/i2p/util/SimpleScheduler.java b/core/java/src/net/i2p/util/SimpleScheduler.java
index f764debe99f6cad3d730c64bae342b9679011d13..951f5929eb5cf6c1489ad053f203f3dc4983e4ac 100644
--- a/core/java/src/net/i2p/util/SimpleScheduler.java
+++ b/core/java/src/net/i2p/util/SimpleScheduler.java
@@ -30,10 +30,10 @@ public class SimpleScheduler {
     public static SimpleScheduler getInstance() { return _instance; }
     private static final int MIN_THREADS = 2;
     private static final int MAX_THREADS = 4;
-    private I2PAppContext _context;
-    private Log _log;
-    private ScheduledThreadPoolExecutor _executor;
-    private String _name;
+    private final I2PAppContext _context;
+    private final Log _log;
+    private final ScheduledThreadPoolExecutor _executor;
+    private final String _name;
     private int _count;
     private final int _threads;
 
@@ -42,7 +42,6 @@ public class SimpleScheduler {
         _context = I2PAppContext.getGlobalContext();
         _log = _context.logManager().getLog(SimpleScheduler.class);
         _name = name;
-        _count = 0;
         long maxMemory = Runtime.getRuntime().maxMemory();
         _threads = (int) Math.max(MIN_THREADS, Math.min(MAX_THREADS, 1 + (maxMemory / (32*1024*1024))));
         _executor = new ScheduledThreadPoolExecutor(_threads, new CustomThreadFactory());
@@ -139,7 +138,7 @@ public class SimpleScheduler {
             try {
                 _timedEvent.timeReached();
             } catch (Throwable t) {
-                _log.log(Log.CRIT, _name + " wtf, event borked: " + _timedEvent, t);
+                _log.log(Log.CRIT, _name + ": Scheduled task " + _timedEvent + " exited unexpectedly, please report", t);
             }
             long time = System.currentTimeMillis() - before;
             if (time > 1000 && _log.shouldLog(Log.WARN))
diff --git a/core/java/src/net/i2p/util/SimpleTimer.java b/core/java/src/net/i2p/util/SimpleTimer.java
index f428afceb91be35badc734c848d48d3aa33123bd..ee3f35120f9b37363d1163cb9b4287706e740229 100644
--- a/core/java/src/net/i2p/util/SimpleTimer.java
+++ b/core/java/src/net/i2p/util/SimpleTimer.java
@@ -14,6 +14,8 @@ import net.i2p.I2PAppContext;
  * appropriate time.  The method that is fired however should NOT block (otherwise
  * they b0rk the timer).
  *
+ * WARNING - Deprecated.
+ * This is an inefficient mess. Use SimpleScheduler or SimpleTimer2 if possible.
  */
 public class SimpleTimer {
     private static final SimpleTimer _instance = new SimpleTimer();
diff --git a/core/java/src/net/i2p/util/SimpleTimer2.java b/core/java/src/net/i2p/util/SimpleTimer2.java
index 44e405b2484bb1287f8154f93e114026f538aaa1..a497915e2358e4db6081a1c91fa5fd54559d27e2 100644
--- a/core/java/src/net/i2p/util/SimpleTimer2.java
+++ b/core/java/src/net/i2p/util/SimpleTimer2.java
@@ -29,10 +29,10 @@ public class SimpleTimer2 {
     public static SimpleTimer2 getInstance() { return _instance; }
     private static final int MIN_THREADS = 2;
     private static final int MAX_THREADS = 4;
-    private I2PAppContext _context;
+    private final I2PAppContext _context;
     private static Log _log; // static so TimedEvent can use it
-    private ScheduledThreadPoolExecutor _executor;
-    private String _name;
+    private final ScheduledThreadPoolExecutor _executor;
+    private final String _name;
     private int _count;
     private final int _threads;
 
@@ -223,7 +223,7 @@ public class SimpleTimer2 {
             try {
                 timeReached();
             } catch (Throwable t) {
-                _log.log(Log.CRIT, _pool + " wtf, event borked: " + this, t);
+                _log.log(Log.CRIT, _pool + ": Timed task " + this + " exited unexpectedly, please report", t);
             }
             long time = System.currentTimeMillis() - before;
             if (time > 500 && _log.shouldLog(Log.WARN))
diff --git a/core/java/test/net/i2p/data/LeaseSetTest.java b/core/java/test/net/i2p/data/LeaseSetTest.java
index 61c3b73c6de2625c97f4427efe47b38f5c3d0aed..30fb8c11fb2041ee3d69076d7b5d1fc4b0f92c22 100644
--- a/core/java/test/net/i2p/data/LeaseSetTest.java
+++ b/core/java/test/net/i2p/data/LeaseSetTest.java
@@ -25,4 +25,50 @@ public class LeaseSetTest extends StructureTest {
         return leaseSet; 
     }
     public DataStructure createStructureToRead() { return new LeaseSet(); }
+    
+    public void testGetLeaseInvalid() {
+        // create test subject
+        LeaseSet subj = new LeaseSet();
+        
+        // should contain no leases now..
+        try {
+            assertNull(subj.getLease(0));
+        } catch(RuntimeException exc) {
+            // all good
+        }
+        
+        // this shouldn't work either
+        try {
+            assertNull(subj.getLease(-1));
+        } catch(RuntimeException exc) {
+            // all good
+        }
+    }
+    
+    public void testAddLeaseNull() {
+        // create test subject
+        LeaseSet subj = new LeaseSet();
+        
+        // now add an null lease
+        try {
+            subj.addLease(null);
+            fail("Failed at failing.");
+        } catch(IllegalArgumentException exc) {
+            // all good
+        }
+    }
+    
+    public void testAddLeaseInvalid() {
+        // create test subject
+        LeaseSet subj = new LeaseSet();
+        
+        // try to add completely invalid lease(ie. no data)
+        try {
+            subj.addLease(new Lease());
+            fail("Failed at failing.");
+        } catch(IllegalArgumentException exc) {
+            // all good
+        }
+    }
+            
 }
diff --git a/core/java/test/net/i2p/data/RouterAddressTest.java b/core/java/test/net/i2p/data/RouterAddressTest.java
index 65a44f92128aff26350a5467bef22d09e17b1718..aab4a88bb9ac593362200f066a2d4774ffd54d57 100644
--- a/core/java/test/net/i2p/data/RouterAddressTest.java
+++ b/core/java/test/net/i2p/data/RouterAddressTest.java
@@ -58,6 +58,7 @@ public class RouterAddressTest extends StructureTest {
         addr.setOptions(options);
         addr.setTransportStyle("Blah");
         assertFalse(addr.equals(null));
+        assertFalse(addr.equals(""));
     }
     
     public void testToString(){
@@ -73,5 +74,7 @@ public class RouterAddressTest extends StructureTest {
         addr.setOptions(options);
         addr.setTransportStyle("Blah");
         addr.toString();
+        addr.setOptions(null);
+        addr.toString();
     }
 }
diff --git a/history.txt b/history.txt
index 967b5b763256c93978ba946d3ead513740a6e6c4..4aa6626fac9656225dd48551d5054d667226b2ce 100644
--- a/history.txt
+++ b/history.txt
@@ -1,9 +1,15 @@
+2011-02-15 zzz
+    * i2psnark: Details link shuffle, mostly restore 0.8.3 behavior
+    * Profiles: Punish rejections more, in an attempt to spread the
+                load more through the network
+    * Timers: Log cleanup
+
 2011-02-14 Mathiasdm
     * Fix headless issue without reboot
 
 2011-02-13 zzz
     * Connect Client: Minor NPE fix cleanup
-    * JobQueue: Prevet NPE at shutdown (thanks liberty)
+    * JobQueue: Prevent NPE at shutdown (thanks liberty)
     * GeoIP: Prevent startup NPE (ticket #413, thanks RN)
     * NetDB: Prevent ExpireLeaseJob NPE (thanks sponge)
 
@@ -21,6 +27,7 @@
     * I2CP: Correctly close internal connections on the router side
             when closed by the client, was causing massive memory leak
             for internal clients using lots of sessions (thanks sponge)
+            (ticket #397)
     * i2psnark:
       - Improved magnet link parsing, use tr parameter if present
     * i2ptunnel: Change shared clients default for new clients to false
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 5a58b19bc22ea9b489b5610aade18177efc4505e..e36cd3b14352a16a1e79c5fea474cb2f20cbaa5c 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 11;
+    public final static long BUILD = 12;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java b/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java
index 9d23ceddf5349216fa082977b47ca235d6a0e3f1..96570260c5de43c09b24efe897e8ca963b4a0b8c 100644
--- a/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java
+++ b/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java
@@ -11,7 +11,7 @@ class CapacityCalculator {
     private static final I2PAppContext _context = I2PAppContext.getGlobalContext();
     
     /** used to adjust each period so that we keep trying to expand the peer's capacity */
-    static long GROWTH_FACTOR = 5;
+    static final long GROWTH_FACTOR = 5;
     
     /** the calculator estimates over a 1 hour period */
     private static long ESTIMATE_PERIOD = 60*60*1000;
@@ -83,37 +83,42 @@ class CapacityCalculator {
      *
      * Let A = accects, R = rejects, F = fails
      * @return estimated and adjusted accepts per hour, for the given period
-     *         which is, more or less, max(0, 5 + (A * (A / (A + R))) - (4 * F))
+     *         which is, more or less, max(0, 5 + (A * (A / (A + 2R))) - (4 * F))
      */
     private static double estimateCapacity(RateStat acceptStat, RateStat rejectStat, RateStat failedStat, int period) {
         Rate curAccepted = acceptStat.getRate(period);
         Rate curRejected = rejectStat.getRate(period);
         Rate curFailed = failedStat.getRate(period);
 
-        long eventCount = 0;
-        if (curAccepted != null)
+        double eventCount = 0;
+        if (curAccepted != null) {
             eventCount = curAccepted.getCurrentEventCount() + curAccepted.getLastEventCount();
-        // Punish for rejections.
-        // We don't want to simply do eventCount -= rejected or we get to zero with 50% rejection,
-        // and we don't want everybody to be at zero during times of congestion.
-        if (eventCount > 0) {
-            long rejected = curRejected.getCurrentEventCount() + curRejected.getLastEventCount();
-            eventCount = eventCount * eventCount / (eventCount + rejected);
+            // Punish for rejections.
+            // We don't want to simply do eventCount -= rejected or we get to zero with 50% rejection,
+            // and we don't want everybody to be at zero during times of congestion.
+            if (eventCount > 0 && curRejected != null) {
+                long rejected = curRejected.getCurrentEventCount() + curRejected.getLastEventCount();
+                if (rejected > 0)
+                    eventCount *= eventCount / (eventCount + (2 * rejected));
+            }
         }
+
         double stretch = ((double)ESTIMATE_PERIOD) / period;
         double val = eventCount * stretch;
-        long failed = 0;
+
         // Let's say a failure is 4 times worse than a rejection.
         // It's actually much worse than that, but with 2-hop tunnels and a 8-peer
         // fast pool, for example, you have a 1/7 chance of being falsely blamed.
         // We also don't want to drive everybody's capacity to zero, that isn't helpful.
-        if (curFailed != null)
-            failed = (long) (0.5 + (4.0 * (curFailed.getCurrentTotalValue() + curFailed.getLastTotalValue()) / 100.0));
-        if (failed > 0) {
-            //if ( (period <= 10*60*1000) && (curFailed.getCurrentEventCount() > 0) )
-            //    return 0.0d; // their tunnels have failed in the last 0-10 minutes
-            //else
-            val -= failed * stretch;
+        if (curFailed != null) {
+            double failed = curFailed.getCurrentTotalValue() + curFailed.getLastTotalValue();
+            if (failed > 0) {
+                //if ( (period <= 10*60*1000) && (curFailed.getCurrentEventCount() > 0) )
+                //    return 0.0d; // their tunnels have failed in the last 0-10 minutes
+                //else
+                // .04 = 4.0 / 100.0 adjustment to failed
+                val -= 0.04 * failed * stretch;
+            }
         }
         
         val += GROWTH_FACTOR;