From 14986fbfa159f403cf82e42dffc68bfff651547a Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Sat, 19 Mar 2011 17:19:54 +0000
Subject: [PATCH]     * Profiles:       - Nicer profile dump       - More
 efficient profile lookup for display       - Fix dumpprofile NPE       -
 Change file suffix from .dat to .txt.gz

---
 .../router/web/ProfileOrganizerRenderer.java  |   6 +-
 .../src/net/i2p/router/web/StatHelper.java    |  70 ++++++++---
 apps/routerconsole/jsp/dumpprofile.jsp        |   3 +-
 apps/routerconsole/jsp/viewprofile.jsp        |  30 +++++
 .../src/net/i2p/stat/PersistenceHelper.java   |  22 +++-
 core/java/src/net/i2p/stat/Rate.java          |  33 +++--
 .../router/peermanager/ProfileOrganizer.java  |   9 +-
 .../peermanager/ProfilePersistenceHelper.java | 119 +++++++++++-------
 .../i2p/router/peermanager/TunnelHistory.java |  20 +--
 9 files changed, 219 insertions(+), 93 deletions(-)
 create mode 100644 apps/routerconsole/jsp/viewprofile.jsp

diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java
index f1b4779ddb..d83720d079 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java
@@ -171,8 +171,10 @@ class ProfileOrganizerRenderer {
                     buf.append(' ').append(fails).append('/').append(total).append(' ').append(_("Test Fails"));
             }
             buf.append("&nbsp;</td>");
-            buf.append("<td nowrap align=\"center\"><a target=\"_blank\" href=\"dumpprofile.jsp?peer=")
-               .append(peer.toBase64().substring(0,6)).append("\">").append(_("profile")).append("</a>");
+            //buf.append("<td nowrap align=\"center\"><a target=\"_blank\" href=\"dumpprofile.jsp?peer=")
+            //   .append(peer.toBase64().substring(0,6)).append("\">").append(_("profile")).append("</a>");
+            buf.append("<td nowrap align=\"center\"><a href=\"viewprofile?peer=")
+               .append(peer.toBase64()).append("\">").append(_("profile")).append("</a>");
             buf.append("&nbsp;<a href=\"configpeer?peer=").append(peer.toBase64()).append("\">+-</a></td>\n");
             buf.append("</tr>");
             // let's not build the whole page in memory (~500 bytes per peer)
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/StatHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/StatHelper.java
index 2b7e81fb41..6cfc7bdcfc 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/StatHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/StatHelper.java
@@ -1,37 +1,73 @@
 package net.i2p.router.web;
 
-import java.util.Iterator;
+import java.io.IOException;
 import java.util.Set;
 
+import net.i2p.data.DataFormatException;
 import net.i2p.data.Hash;
-import net.i2p.router.RouterContext;
 
 /**
- * uuuugly.  dump the peer profile data if given a peer.
+ *  Dump the peer profile data if given a full B64 peer string or prefix.
  *
  */
 public class StatHelper extends HelperBase {
     private String _peer;
     
+    /**
+     * Caller should strip HTML (XSS)
+     */
     public void setPeer(String peer) { _peer = peer; }
     
+    /**
+     *  Look up based on a b64 prefix or full b64.
+     *  Prefix is inefficient.
+     */
     public String getProfile() { 
-        RouterContext ctx = (RouterContext)net.i2p.router.RouterContext.listContexts().get(0);
-        Set peers = ctx.profileOrganizer().selectAllPeers();
-        for (Iterator iter = peers.iterator(); iter.hasNext(); ) {
-            Hash peer = (Hash)iter.next();
+        if (_peer == null || _peer.length() <= 0)
+            return "No peer specified";
+        if (_peer.length() >= 44)
+            return outputProfile();
+        Set<Hash> peers = _context.profileOrganizer().selectAllPeers();
+        for (Hash peer : peers) {
             if (peer.toBase64().startsWith(_peer)) {
-                try {
-                    WriterOutputStream wos = new WriterOutputStream(_out);
-                    ctx.profileOrganizer().exportProfile(peer, wos);
-                    wos.flush();
-                    _out.flush();
-                    return "";
-                } catch (Exception e) {
-                    e.printStackTrace();
-                }
+                return dumpProfile(peer);
             }
         }
-        return "Unknown";
+        return "Unknown peer " + _peer;
+    }
+
+    /**
+     *  Look up based on the full b64 - efficient
+     *  @since 0.8.5
+     */
+    private String outputProfile() { 
+        Hash peer = new Hash();
+        try {
+            peer.fromBase64(_peer);
+            return dumpProfile(peer);
+        } catch (DataFormatException dfe) {
+            return "Bad peer hash " + _peer;
+        }
+    }
+
+    /**
+     *  dump the profile
+     *  @since 0.8.5
+     */
+    private String dumpProfile(Hash peer) { 
+        try {
+            WriterOutputStream wos = new WriterOutputStream(_out);
+            boolean success = _context.profileOrganizer().exportProfile(peer, wos);
+            if (success) {
+                wos.flush();
+                _out.flush();
+                return "";
+            } else {
+                return "Unknown peer " + _peer;
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            return "IO Error " + e;
+        }
     }
 }
diff --git a/apps/routerconsole/jsp/dumpprofile.jsp b/apps/routerconsole/jsp/dumpprofile.jsp
index dfdcc0b6e5..af6c8de744 100644
--- a/apps/routerconsole/jsp/dumpprofile.jsp
+++ b/apps/routerconsole/jsp/dumpprofile.jsp
@@ -1,5 +1,6 @@
 <%@page contentType="text/plain"
 %><jsp:useBean id="helper" class="net.i2p.router.web.StatHelper"
-/><jsp:setProperty name="helper" property="peer" value="<%=request.getParameter("peer")%>"
+/><jsp:setProperty name="helper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>"
+/><jsp:setProperty name="helper" property="peer" value="<%=net.i2p.data.DataHelper.stripHTML(request.getParameter("peer"))%>"
 /><% helper.storeWriter(out);
 %><jsp:getProperty name="helper" property="profile" />
diff --git a/apps/routerconsole/jsp/viewprofile.jsp b/apps/routerconsole/jsp/viewprofile.jsp
new file mode 100644
index 0000000000..36897d4a9a
--- /dev/null
+++ b/apps/routerconsole/jsp/viewprofile.jsp
@@ -0,0 +1,30 @@
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<%@include file="css.jsi" %>
+<%=intl.title("Peer Profile")%>
+</head><body>
+<%@include file="summary.jsi" %>
+<h1><%=intl._("Peer Profile")%></h1>
+<div class="main" id="main"><div class="wideload">
+<%
+    String peerB64 = request.getParameter("peer");
+    if (peerB64 == null || peerB64.length() <= 0) {
+        out.print("No peer specified");
+    } else {
+        peerB64 = net.i2p.data.DataHelper.stripHTML(peerB64);  // XSS
+%>
+<jsp:useBean id="stathelper" class="net.i2p.router.web.StatHelper" />
+<jsp:setProperty name="stathelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+<jsp:setProperty name="stathelper" property="peer" value="<%=peerB64%>" />
+<% stathelper.storeWriter(out); %>
+<h2><%=intl._("Profile for peer {0}", peerB64)%></h2>
+<pre>
+<jsp:getProperty name="stathelper" property="profile" />
+</pre>
+<%
+    }
+%>
+</div></div></body></html>
diff --git a/core/java/src/net/i2p/stat/PersistenceHelper.java b/core/java/src/net/i2p/stat/PersistenceHelper.java
index d8c1312d42..1d5c6a4663 100644
--- a/core/java/src/net/i2p/stat/PersistenceHelper.java
+++ b/core/java/src/net/i2p/stat/PersistenceHelper.java
@@ -1,10 +1,16 @@
 package net.i2p.stat;
 
+import java.util.Date;
 import java.util.Properties;
 
+import net.i2p.data.DataHelper;
 import net.i2p.util.Log;
 
-/** object orientation gives you hairy palms. */
+/**
+ *  Output rate data.
+ *  This is used via ProfilePersistenceHelper and the output
+ *  must be compatible.
+ */
 class PersistenceHelper {
     private final static Log _log = new Log(PersistenceHelper.class);
     private final static String NL = System.getProperty("line.separator");
@@ -15,6 +21,18 @@ class PersistenceHelper {
         buf.append(prefix).append(name).append('=').append(value).append(NL).append(NL);
     }
 
+    /** @since 0.8.5 */
+    public final static void addDate(StringBuilder buf, String prefix, String name, String description, long value) {
+        String when = value > 0 ? (new Date(value)).toString() : "Never";
+        add(buf, prefix, name, description + ' ' + when, value);
+    }
+
+    /** @since 0.8.5 */
+    public final static void addTime(StringBuilder buf, String prefix, String name, String description, long value) {
+        String when = DataHelper.formatDuration(value);
+        add(buf, prefix, name, description + ' ' + when, value);
+    }
+
     public final static void add(StringBuilder buf, String prefix, String name, String description, long value) {
         buf.append("# ").append(prefix).append(name).append(NL);
         buf.append("# ").append(description).append(NL);
@@ -48,4 +66,4 @@ class PersistenceHelper {
         }
         return 0;
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/src/net/i2p/stat/Rate.java b/core/java/src/net/i2p/stat/Rate.java
index 21e496739a..8bf5bcf472 100644
--- a/core/java/src/net/i2p/stat/Rate.java
+++ b/core/java/src/net/i2p/stat/Rate.java
@@ -400,42 +400,41 @@ public class Rate {
     }
 
     public void store(String prefix, StringBuilder buf) throws IOException {
-        PersistenceHelper.add(buf, prefix, ".period", "Number of milliseconds in the period", _period);
-        PersistenceHelper.add(buf, prefix, ".creationDate",
-                              "When was this rate created?  (milliseconds since the epoch, GMT)", _creationDate);
-        PersistenceHelper.add(buf, prefix, ".lastCoalesceDate",
-                              "When did we last coalesce this rate?  (milliseconds since the epoch, GMT)",
+        PersistenceHelper.addTime(buf, prefix, ".period", "Length of the period:", _period);
+        PersistenceHelper.addDate(buf, prefix, ".creationDate",
+                              "When was this rate created?", _creationDate);
+        PersistenceHelper.addDate(buf, prefix, ".lastCoalesceDate",
+                              "When did we last coalesce this rate?",
                               _lastCoalesceDate);
-        PersistenceHelper.add(buf, prefix, ".currentDate",
-                              "When did this data get written?  (milliseconds since the epoch, GMT)", now());
+        PersistenceHelper.addDate(buf, prefix, ".currentDate",
+                              "When was this data written?", now());
         PersistenceHelper.add(buf, prefix, ".currentTotalValue",
                               "Total value of data points in the current (uncoalesced) period", _currentTotalValue);
-        PersistenceHelper
-                         .add(buf, prefix, ".currentEventCount",
+        PersistenceHelper.add(buf, prefix, ".currentEventCount",
                               "How many events have occurred in the current (uncoalesced) period?", _currentEventCount);
-        PersistenceHelper.add(buf, prefix, ".currentTotalEventTime",
-                              "How many milliseconds have the events in the current (uncoalesced) period consumed?",
+        PersistenceHelper.addTime(buf, prefix, ".currentTotalEventTime",
+                              "How much time have the events in the current (uncoalesced) period consumed?",
                               _currentTotalEventTime);
         PersistenceHelper.add(buf, prefix, ".lastTotalValue",
                               "Total value of data points in the most recent (coalesced) period", _lastTotalValue);
         PersistenceHelper.add(buf, prefix, ".lastEventCount",
                               "How many events have occurred in the most recent (coalesced) period?", _lastEventCount);
-        PersistenceHelper.add(buf, prefix, ".lastTotalEventTime",
-                              "How many milliseconds have the events in the most recent (coalesced) period consumed?",
+        PersistenceHelper.addTime(buf, prefix, ".lastTotalEventTime",
+                              "How much time have the events in the most recent (coalesced) period consumed?",
                               _lastTotalEventTime);
         PersistenceHelper.add(buf, prefix, ".extremeTotalValue",
                               "Total value of data points in the most extreme period", _extremeTotalValue);
         PersistenceHelper.add(buf, prefix, ".extremeEventCount",
                               "How many events have occurred in the most extreme period?", _extremeEventCount);
-        PersistenceHelper.add(buf, prefix, ".extremeTotalEventTime",
-                              "How many milliseconds have the events in the most extreme period consumed?",
+        PersistenceHelper.addTime(buf, prefix, ".extremeTotalEventTime",
+                              "How much time have the events in the most extreme period consumed?",
                               _extremeTotalEventTime);
         PersistenceHelper.add(buf, prefix, ".lifetimeTotalValue",
                               "Total value of data points since this stat was created", _lifetimeTotalValue);
         PersistenceHelper.add(buf, prefix, ".lifetimeEventCount",
                               "How many events have occurred since this stat was created?", _lifetimeEventCount);
-        PersistenceHelper.add(buf, prefix, ".lifetimeTotalEventTime",
-                              "How many milliseconds have the events since this stat was created consumed?",
+        PersistenceHelper.addTime(buf, prefix, ".lifetimeTotalEventTime",
+                              "How much total time was consumed by the events since this stat was created?",
                               _lifetimeTotalEventTime);
     }
 
diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
index fe33c3447f..31fd6c3742 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
@@ -256,10 +256,15 @@ public class ProfileOrganizer {
         return false;
     }
     
-    public void exportProfile(Hash profile, OutputStream out) throws IOException {
+    /**
+     *  @return true if successful, false if not found
+     */
+    public boolean exportProfile(Hash profile, OutputStream out) throws IOException {
         PeerProfile prof = getProfile(profile);
-        if (prof != null)
+        boolean rv = prof != null;
+        if (rv)
             _persistenceHelper.writeProfile(prof, out);
+        return rv;
     }
     
     /**
diff --git a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
index 592c1f2677..07782fe254 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
@@ -8,6 +8,7 @@ import java.io.FilenameFilter;
 import java.io.InputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Properties;
@@ -24,13 +25,24 @@ import net.i2p.util.Log;
 import net.i2p.util.SecureDirectory;
 import net.i2p.util.SecureFileOutputStream;
 
+/**
+ *  Write profiles to disk at shutdown,
+ *  read at startup.
+ *  The files are gzip compressed, however we unfortunately store them
+ *  with a ".dat" extension instead of ".txt.gz", so it isn't apparent.
+ *  TODO: Migrate to new extension.
+ */
 class ProfilePersistenceHelper {
-    private Log _log;
-    private RouterContext _context;
+    private final Log _log;
+    private final RouterContext _context;
     
     public final static String PROP_PEER_PROFILE_DIR = "router.profileDir";
     public final static String DEFAULT_PEER_PROFILE_DIR = "peerProfiles";
     private final static String NL = System.getProperty("line.separator");
+    private static final String PREFIX = "profile-";
+    private static final String SUFFIX = ".txt.gz";
+    private static final String UNCOMPRESSED_SUFFIX = ".txt";
+    private static final String OLD_SUFFIX = ".dat";
     
     /**
      * If we haven't been able to get a message through to the peer in 3 days,
@@ -76,58 +88,49 @@ class ProfilePersistenceHelper {
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Writing the profile to " + f.getName() + " took " + delay + "ms");
     }
+
     /** write out the data from the profile to the stream */
     public void writeProfile(PeerProfile profile, OutputStream out) throws IOException {
         String groups = null;
         if (_context.profileOrganizer().isFailing(profile.getPeer())) {
-            groups = "failing";
+            groups = "Failing";
         } else if (!_context.profileOrganizer().isHighCapacity(profile.getPeer())) {
-            groups = "not failing";
+            groups = "Standard";
         } else {
             if (_context.profileOrganizer().isFast(profile.getPeer()))
-                groups = "fast and high capacity";
+                groups = "Fast, High Capacity";
             else
-                groups = "high capacity";
+                groups = "High Capacity";
             
             if (_context.profileOrganizer().isWellIntegrated(profile.getPeer()))
-                groups = groups + ", well integrated";
+                groups = groups + ", Integrated";
         }
         
         StringBuilder buf = new StringBuilder(512);
         buf.append("########################################################################").append(NL);
-        buf.append("# profile for ").append(profile.getPeer().toBase64()).append(NL);
+        buf.append("# Profile for peer ").append(profile.getPeer().toBase64()).append(NL);
         if (_us != null)
             buf.append("# as calculated by ").append(_us.toBase64()).append(NL);
         buf.append("#").append(NL);
-        buf.append("# capacity: ").append(profile.getCapacityValue()).append(NL);
-        buf.append("# integration: ").append(profile.getIntegrationValue()).append(NL);
-        buf.append("# speedValue: ").append(profile.getSpeedValue()).append(NL);
-        buf.append("#").append(NL);
+        buf.append("# Speed: ").append(profile.getSpeedValue()).append(NL);
+        buf.append("# Capacity: ").append(profile.getCapacityValue()).append(NL);
+        buf.append("# Integration: ").append(profile.getIntegrationValue()).append(NL);
         buf.append("# Groups: ").append(groups).append(NL);
+        buf.append("#").append(NL);
         buf.append("########################################################################").append(NL);
         buf.append("##").append(NL);
-        buf.append("# Capacity bonus: used to affect the capacity score after all other calculations are done").append(NL);
-        buf.append("capacityBonus=").append(profile.getCapacityBonus()).append(NL);
-        buf.append("# Integration bonus: used to affect the integration score after all other calculations are done").append(NL);
-        buf.append("integrationBonus=").append(profile.getIntegrationBonus()).append(NL);
-        buf.append("# Speed bonus: used to affect the speed score after all other calculations are done").append(NL);
-        buf.append("speedBonus=").append(profile.getSpeedBonus()).append(NL);
-        buf.append(NL).append(NL);
-        buf.append("# Last heard about: when did we last get a reference to this peer?  (milliseconds since the epoch)").append(NL);
-        buf.append("lastHeardAbout=").append(profile.getLastHeardAbout()).append(NL);
-        buf.append("# First heard about: when did we first get a reference to this peer?  (milliseconds since the epoch)").append(NL);
-        buf.append("firstHeardAbout=").append(profile.getFirstHeardAbout()).append(NL);
-        buf.append("# Last sent to successfully: when did we last send the peer a message successfully?  (milliseconds from the epoch)").append(NL);
-        buf.append("lastSentToSuccessfully=").append(profile.getLastSendSuccessful()).append(NL);
-        buf.append("# Last failed send: when did we last fail to send a message to the peer?  (milliseconds from the epoch)").append(NL);
-        buf.append("lastFailedSend=").append(profile.getLastSendFailed()).append(NL);
-        buf.append("# Last heard from: when did we last get a message from the peer?  (milliseconds from the epoch)").append(NL);
-        buf.append("lastHeardFrom=").append(profile.getLastHeardFrom()).append(NL);
-        buf.append("# moving average as to how fast the peer replies").append(NL);
-        buf.append("tunnelTestTimeAverage=").append(profile.getTunnelTestTimeAverage()).append(NL);
-        buf.append("tunnelPeakThroughput=").append(profile.getPeakThroughputKBps()).append(NL);
-        buf.append("tunnelPeakTunnelThroughput=").append(profile.getPeakTunnelThroughputKBps()).append(NL);
-        buf.append("tunnelPeakTunnel1mThroughput=").append(profile.getPeakTunnel1mThroughputKBps()).append(NL);
+        add(buf, "capacityBonus", profile.getCapacityBonus(), "Manual adjustment to the capacity score");
+        add(buf, "integrationBonus", profile.getIntegrationBonus(), "Manual adjustment to the integration score");
+        add(buf, "speedBonus", profile.getSpeedBonus(), "Manual adjustment to the speed score");
+        addDate(buf, "lastHeardAbout", profile.getLastHeardAbout(), "When did we last get a reference to this peer?");
+        addDate(buf, "firstHeardAbout", profile.getFirstHeardAbout(), "When did we first get a reference to this peer?");
+        addDate(buf, "lastSentToSuccessfully", profile.getLastSendSuccessful(), "When did we last send the peer a message successfully?");
+        addDate(buf, "lastFailedSend", profile.getLastSendFailed(), "When did we last fail to send a message to the peer?");
+        addDate(buf, "lastHeardFrom", profile.getLastHeardFrom(), "When did we last get a message from the peer?");
+        add(buf, "tunnelTestTimeAverage", profile.getTunnelTestTimeAverage(), "Moving average as to how fast the peer replies");
+        add(buf, "tunnelPeakThroughput", profile.getPeakThroughputKBps(), "KBytes/sec");
+        add(buf, "tunnelPeakTunnelThroughput", profile.getPeakTunnelThroughputKBps(), "KBytes/sec");
+        add(buf, "tunnelPeakTunnel1mThroughput", profile.getPeakTunnel1mThroughputKBps(), "KBytes/sec");
         buf.append(NL);
         
         out.write(buf.toString().getBytes());
@@ -148,12 +151,29 @@ class ProfilePersistenceHelper {
         }
     }
     
-    public Set readProfiles() {
+    /** @since 0.8.5 */
+    private static void addDate(StringBuilder buf, String name, long val, String description) {
+        String when = val > 0 ? (new Date(val)).toString() : "Never";
+        add(buf, name, val, description + ' ' + when);
+    }
+    
+    /** @since 0.8.5 */
+    private static void add(StringBuilder buf, String name, long val, String description) {
+        buf.append("# ").append(name).append(NL).append("# ").append(description).append(NL);
+        buf.append(name).append('=').append(val).append(NL).append(NL);
+    }
+    
+    /** @since 0.8.5 */
+    private static void add(StringBuilder buf, String name, double val, String description) {
+        buf.append("# ").append(name).append(NL).append("# ").append(description).append(NL);
+        buf.append(name).append('=').append(val).append(NL).append(NL);
+    }
+    
+    public Set<PeerProfile> readProfiles() {
         long start = _context.clock().now();
-        Set files = selectFiles();
-        Set profiles = new HashSet(files.size());
-        for (Iterator iter = files.iterator(); iter.hasNext();) {
-            File f = (File)iter.next();
+        Set<File> files = selectFiles();
+        Set<PeerProfile> profiles = new HashSet(files.size());
+        for (File f :  files) {
             PeerProfile profile = readProfile(f);
             if (profile != null)
                 profiles.add(profile);
@@ -164,10 +184,11 @@ class ProfilePersistenceHelper {
         return profiles;
     }
     
-    private Set selectFiles() {
+    private Set<File> selectFiles() {
         File files[] = getProfileDir().listFiles(new FilenameFilter() {
             public boolean accept(File dir, String filename) {
-                return (filename.startsWith("profile-") && filename.endsWith(".dat"));
+                return (filename.startsWith(PREFIX) &&
+                        (filename.endsWith(SUFFIX) || filename.endsWith(OLD_SUFFIX) || filename.endsWith(UNCOMPRESSED_SUFFIX)));
             }
         });
         Set rv = new HashSet(files.length);
@@ -200,6 +221,14 @@ class ProfilePersistenceHelper {
                               ", since we haven't heard from them in a long time");
                 file.delete();
                 return null;
+            } else if (file.getName().endsWith(OLD_SUFFIX)) {
+                // migrate to new file name, ignore failure
+                String newName = file.getAbsolutePath();
+                newName = newName.substring(0, newName.length() - OLD_SUFFIX.length()) + SUFFIX;
+                boolean success = file.renameTo(new File(newName));
+                if (!success)
+                    // new file exists and on Windows?
+                    file.delete();
             }
             
             profile.setCapacityBonus(getLong(props, "capacityBonus"));
@@ -300,8 +329,8 @@ class ProfilePersistenceHelper {
     }
 
     private Hash getHash(String name) {
-        String key = name.substring("profile-".length());
-        key = key.substring(0, key.length() - ".dat".length());
+        String key = name.substring(PREFIX.length());
+        key = key.substring(0, 44);
         //Hash h = new Hash();
         try {
             //h.fromBase64(key);
@@ -317,7 +346,7 @@ class ProfilePersistenceHelper {
     }
     
     private File pickFile(PeerProfile profile) {
-        return new File(getProfileDir(), "profile-" + profile.getPeer().toBase64() + ".dat");
+        return new File(getProfileDir(), PREFIX + profile.getPeer().toBase64() + SUFFIX);
     }
     
     private File getProfileDir() {
@@ -339,7 +368,7 @@ class ProfilePersistenceHelper {
             rnd.nextBytes(data);
             Hash peer = new Hash(data);
             try {
-                File f = new File(dir, "profile-" + peer.toBase64() + ".dat");
+                File f = new File(dir, PREFIX + peer.toBase64() + SUFFIX);
                 f.createNewFile();
                 System.out.println("Created " + peer.toBase64());
             } catch (IOException ioe) {}
diff --git a/router/java/src/net/i2p/router/peermanager/TunnelHistory.java b/router/java/src/net/i2p/router/peermanager/TunnelHistory.java
index 208415ca0b..bc6109bcad 100644
--- a/router/java/src/net/i2p/router/peermanager/TunnelHistory.java
+++ b/router/java/src/net/i2p/router/peermanager/TunnelHistory.java
@@ -2,6 +2,7 @@ package net.i2p.router.peermanager;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Date;
 import java.util.Properties;
 
 import net.i2p.router.RouterContext;
@@ -138,12 +139,12 @@ public class TunnelHistory {
         buf.append("#################").append(NL);
         buf.append("# Tunnel history").append(NL);
         buf.append("###").append(NL);
-        add(buf, "lastAgreedTo", _lastAgreedTo, "When did the peer last agree to participate in a tunnel?  (milliseconds since the epoch)");
-        add(buf, "lastFailed", _lastFailed, "When was the last time a tunnel that the peer agreed to participate failed?  (milliseconds since the epoch)");
-        add(buf, "lastRejectedCritical", _lastRejectedCritical, "When was the last time the peer refused to participate in a tunnel?  (milliseconds since the epoch)");
-        add(buf, "lastRejectedBandwidth", _lastRejectedBandwidth, "When was the last time the peer refused to participate in a tunnel?  (milliseconds since the epoch)");
-        add(buf, "lastRejectedTransient", _lastRejectedTransient, "When was the last time the peer refused to participate in a tunnel?  (milliseconds since the epoch)");
-        add(buf, "lastRejectedProbabalistic", _lastRejectedProbabalistic, "When was the last time the peer refused to participate in a tunnel?  (milliseconds since the epoch)");
+        addDate(buf, "lastAgreedTo", _lastAgreedTo, "When did the peer last agree to participate in a tunnel?");
+        addDate(buf, "lastFailed", _lastFailed, "When was the last time a tunnel that the peer agreed to participate failed?");
+        addDate(buf, "lastRejectedCritical", _lastRejectedCritical, "When was the last time the peer refused to participate in a tunnel (Critical response code)?");
+        addDate(buf, "lastRejectedBandwidth", _lastRejectedBandwidth, "When was the last time the peer refused to participate in a tunnel (Bandwidth response code)?");
+        addDate(buf, "lastRejectedTransient", _lastRejectedTransient, "When was the last time the peer refused to participate in a tunnel (Transient load response code)?");
+        addDate(buf, "lastRejectedProbabalistic", _lastRejectedProbabalistic, "When was the last time the peer refused to participate in a tunnel (Probabalistic response code)?");
         add(buf, "lifetimeAgreedTo", _lifetimeAgreedTo, "How many tunnels has the peer ever agreed to participate in?");
         add(buf, "lifetimeFailed", _lifetimeFailed, "How many tunnels has the peer ever agreed to participate in that failed prematurely?");
         add(buf, "lifetimeRejected", _lifetimeRejected, "How many tunnels has the peer ever refused to participate in?");
@@ -152,8 +153,13 @@ public class TunnelHistory {
         _failRate.store(out, "tunnelHistory.failRate");
     }
     
+    private static void addDate(StringBuilder buf, String name, long val, String description) {
+        String when = val > 0 ? (new Date(val)).toString() : "Never";
+        add(buf, name, val, description + ' ' + when);
+    }
+    
     private static void add(StringBuilder buf, String name, long val, String description) {
-        buf.append("# ").append(name.toUpperCase()).append(NL).append("# ").append(description).append(NL);
+        buf.append("# ").append(name).append(NL).append("# ").append(description).append(NL);
         buf.append("tunnels.").append(name).append('=').append(val).append(NL).append(NL);
     }
     
-- 
GitLab