Package freenet.clients.http

Source Code of freenet.clients.http.ConnectionsToadlet$SimpleColumn

package freenet.clients.http;

import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import freenet.client.HighLevelSimpleClient;
import freenet.clients.fcp.AddPeer;
import freenet.clients.http.geoip.IPConverter;
import freenet.clients.http.geoip.IPConverter.Country;
import freenet.io.comm.PeerParseException;
import freenet.io.comm.ReferenceSignatureVerificationException;
import freenet.io.xfer.PacketThrottle;
import freenet.l10n.NodeL10n;
import freenet.node.DarknetPeerNode;
import freenet.node.DarknetPeerNode.FRIEND_VISIBILITY;
import freenet.node.DarknetPeerNode.FRIEND_TRUST;
import freenet.node.FSParseException;
import freenet.node.Node;
import freenet.node.NodeClientCore;
import freenet.node.NodeStats;
import freenet.node.PeerManager;
import freenet.node.PeerNode;
import freenet.node.PeerNode.IncomingLoadSummaryStats;
import freenet.node.PeerNodeStatus;
import freenet.node.Version;
import freenet.node.updater.NodeUpdateManager;
import freenet.support.Fields;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.MultiValueTable;
import freenet.support.SimpleFieldSet;
import freenet.support.SizeUtil;
import freenet.support.TimeUtil;
import freenet.support.api.HTTPRequest;
import freenet.support.io.Closer;

/** Base class for DarknetConnectionsToadlet and OpennetConnectionsToadlet */
public abstract class ConnectionsToadlet extends Toadlet {
  protected class ComparatorByStatus implements Comparator<PeerNodeStatus> {   
    protected final String sortBy;
    protected final boolean reversed;
   
    ComparatorByStatus(String sortBy, boolean reversed) {
      this.sortBy = sortBy;
      this.reversed = reversed;
    }
   
    @Override
    public int compare(PeerNodeStatus firstNode, PeerNodeStatus secondNode) {
      int result = 0;
      boolean isSet = true;
     
      if(sortBy != null){
        result = customCompare(firstNode, secondNode, sortBy);
        isSet = (result != 0);
       
      }else
        isSet=false;
     
      if(!isSet){
        int statusDifference = firstNode.getStatusValue() - secondNode.getStatusValue();
        if (statusDifference != 0)
          result = (statusDifference < 0 ? -1 : 1);
        else
          result = lastResortCompare(firstNode, secondNode);
      }

      if(result == 0){
        return 0;
      }else if(reversed){
        isReversed = true;
        return result > 0 ? -1 : 1;
      }else{
        isReversed = false;
        return result < 0 ? -1 : 1;
      }
    }
   
    // xor: check why we do not just return the result of (long1-long2)
    // j16sdiz: (Long.MAX_VALUE - (-1) ) would overflow and become negative
    private int compareLongs(long long1, long long2) {
      int diff = Long.valueOf(long1).compareTo(long2);
      if(diff == 0)
        return 0;
      else
        return (diff > 0 ? 1 : -1);
    }
   
    private int compareInts(int int1, int int2) {
      int diff = Integer.valueOf(int1).compareTo(int2);
      if(diff == 0)
        return 0;
      else
        return (diff > 0 ? 1 : -1);
    }

    protected int customCompare(PeerNodeStatus firstNode, PeerNodeStatus secondNode, String sortBy2) {
      if(sortBy.equals("address")){
        return firstNode.getPeerAddress().compareToIgnoreCase(secondNode.getPeerAddress());
      }else if(sortBy.equals("location")){
        return compareLocations(firstNode, secondNode);
      }else if(sortBy.equals("version")){
        return Version.getArbitraryBuildNumber(firstNode.getVersion(), -1) - Version.getArbitraryBuildNumber(secondNode.getVersion(), -1);
      }else if(sortBy.equals("backoffRT")){
        return Double.compare(firstNode.getBackedOffPercent(true), secondNode.getBackedOffPercent(true));
      }else if(sortBy.equals("backoffBulk")){
        return Double.compare(firstNode.getBackedOffPercent(false), secondNode.getBackedOffPercent(false));
      }else if(sortBy.equals(("overload_p"))){
        return Double.compare(firstNode.getPReject(), secondNode.getPReject());
      }else if(sortBy.equals(("idle"))){
        return compareLongs(firstNode.getTimeLastConnectionCompleted(), secondNode.getTimeLastConnectionCompleted());
      }else if(sortBy.equals("time_routable")){
        return Double.compare(firstNode.getPercentTimeRoutableConnection(), secondNode.getPercentTimeRoutableConnection());
      }else if(sortBy.equals("total_traffic")){
        long total1 = firstNode.getTotalInputBytes()+firstNode.getTotalOutputBytes();
        long total2 = secondNode.getTotalInputBytes()+secondNode.getTotalOutputBytes();
        return compareLongs(total1, total2);
        }else if(sortBy.equals("total_traffic_since_startup")){
          long total1 = firstNode.getTotalInputSinceStartup()+firstNode.getTotalOutputSinceStartup();
          long total2 = secondNode.getTotalInputSinceStartup()+secondNode.getTotalOutputSinceStartup();
          return compareLongs(total1, total2);
      }else if(sortBy.equals("selection_percentage")){
        return Double.compare(firstNode.getSelectionRate(), secondNode.getSelectionRate());
      }else if(sortBy.equals("time_delta")){
        return compareLongs(firstNode.getClockDelta(), secondNode.getClockDelta());
      }else if(sortBy.equals(("uptime"))){
        return compareInts(firstNode.getReportedUptimePercentage(), secondNode.getReportedUptimePercentage());
      }else
        return 0;
    }

    private int compareLocations(PeerNodeStatus firstNode, PeerNodeStatus secondNode) {
      double diff = firstNode.getLocation() - secondNode.getLocation(); // Can occasionally be the same, and we must have a consistent sort order
      if(Double.MIN_VALUE*2 > Math.abs(diff)) return 0;
      return diff > 0 ? 1 : -1;
    }

    /** Default comparison, after taking into account status */
    protected int lastResortCompare(PeerNodeStatus firstNode, PeerNodeStatus secondNode) {
      return compareLocations(firstNode, secondNode);
    }
  }

  protected final Node node;
  protected final NodeClientCore core;
  protected final NodeStats stats;
  protected final PeerManager peers;
  protected boolean isReversed = false;
  protected boolean showTrivialFoafConnections = false;

  public enum PeerAdditionReturnCodes{ OK, WRONG_ENCODING, CANT_PARSE, INTERNAL_ERROR, INVALID_SIGNATURE, TRY_TO_ADD_SELF, ALREADY_IN_REFERENCE}

  protected ConnectionsToadlet(Node n, NodeClientCore core, HighLevelSimpleClient client) {
    super(client);
    this.node = n;
    this.core = core;
    this.stats = n.nodeStats;
    this.peers = n.peers;
      REF_LINK = HTMLNode.link(path()+"myref.fref").setReadOnly();
      REFTEXT_LINK = HTMLNode.link(path()+"myref.txt").setReadOnly();
  }

  abstract SimpleColumn[] endColumnHeaders(boolean advancedModeEnabled);
 
  abstract class SimpleColumn {
    abstract protected void drawColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus);
    abstract public String getSortString();
    abstract public String getTitleKey();
    abstract public String getExplanationKey();
  }

  public void handleMethodGET(URI uri, final HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException, RedirectException {
        if(!ctx.checkFullAccess(this))
            return;

      String path = uri.getPath();
    if(path.endsWith("myref.fref")) {
      SimpleFieldSet fs = getNoderef();
      String noderefString = fs.toOrderedStringWithBase64();
      MultiValueTable<String, String> extraHeaders = new MultiValueTable<String, String>();
      // Force download to disk
      extraHeaders.put("Content-Disposition", "attachment; filename=myref.fref");
      writeReply(ctx, 200, "application/x-freenet-reference", "OK", extraHeaders, noderefString);
      return;
    }

    if(path.endsWith("myref.txt")) {
      SimpleFieldSet fs = getNoderef();
            String noderefString = fs.toOrderedStringWithBase64();
      writeTextReply(ctx, 200, "OK", noderefString);
      return;
    }
   
    final DecimalFormat fix1 = new DecimalFormat("##0.0%");
       
    final boolean fProxyJavascriptEnabled = node.isFProxyJavascriptEnabled();
    boolean drawMessageTypes = path.endsWith("displaymessagetypes.html");
   
    /* gather connection statistics */
    PeerNodeStatus[] peerNodeStatuses = getPeerNodeStatuses(!drawMessageTypes);
    Arrays.sort(peerNodeStatuses, comparator(request.getParam("sortBy", null), request.isParameterSet("reversed")));
   
    int numberOfConnected = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_CONNECTED);
    int numberOfRoutingBackedOff = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF);
    int numberOfTooNew = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_TOO_NEW);
    int numberOfTooOld = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_TOO_OLD);
    int numberOfDisconnected = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_DISCONNECTED);
    int numberOfNeverConnected = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED);
    int numberOfDisabled = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_DISABLED);
    int numberOfBursting = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_BURSTING);
    int numberOfListening = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_LISTENING);
    int numberOfListenOnly = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_LISTEN_ONLY);
    int numberOfClockProblem = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_CLOCK_PROBLEM);
    int numberOfConnError = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_CONN_ERROR);
    int numberOfDisconnecting = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_DISCONNECTING);
    int numberOfRoutingDisabled = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_ROUTING_DISABLED);
    int numberOfNoLoadStats = PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, PeerManager.PEER_NODE_STATUS_NO_LOAD_STATS);
   
    int numberOfSimpleConnected = numberOfConnected + numberOfRoutingBackedOff;
    int numberOfNotConnected = numberOfTooNew + numberOfTooOld +  numberOfNoLoadStats + numberOfDisconnected + numberOfNeverConnected + numberOfDisabled + numberOfBursting + numberOfListening + numberOfListenOnly + numberOfClockProblem + numberOfConnError;
    String titleCountString = null;
    if(node.isAdvancedModeEnabled()) {
      titleCountString = "(" + numberOfConnected + '/' + numberOfRoutingBackedOff + '/' + numberOfTooNew + '/' + numberOfTooOld + '/' + numberOfNoLoadStats + '/' + numberOfRoutingDisabled + '/' + numberOfNotConnected + ')';
    } else {
      titleCountString = (numberOfNotConnected + numberOfSimpleConnected)>0 ? String.valueOf(numberOfSimpleConnected) : "";
    }

    PageNode page = ctx.getPageMaker().getPageNode(getPageTitle(titleCountString), ctx);
    final boolean advancedMode = ctx.isAdvancedModeEnabled();
    HTMLNode pageNode = page.outer;
    HTMLNode contentNode = page.content;
   
    // FIXME! We need some nice images
    long now = System.currentTimeMillis();
 
    if(ctx.isAllowedFullAccess())
      contentNode.addChild(ctx.getAlertManager().createSummary());
   
    if(peerNodeStatuses.length>0){
     
      if(advancedMode) {

        /* node status values */
        long nodeUptimeSeconds = SECONDS.convert(now - node.startupTime, MILLISECONDS);
        int bwlimitDelayTime = (int) stats.getBwlimitDelayTime();
        int nodeAveragePingTime = (int) stats.getNodeAveragePingTime();
        int networkSizeEstimateSession = stats.getDarknetSizeEstimate(-1);
        int networkSizeEstimateRecent = 0;
        if(nodeUptimeSeconds > HOURS.toSeconds(48)) {
          networkSizeEstimateRecent = stats.getDarknetSizeEstimate(now - HOURS.toMillis(48));
        }
        DecimalFormat fix4 = new DecimalFormat("0.0000");
        double routingMissDistanceLocal =  stats.routingMissDistanceLocal.currentValue();
        double routingMissDistanceRemote =  stats.routingMissDistanceRemote.currentValue();
        double routingMissDistanceOverall =  stats.routingMissDistanceOverall.currentValue();
        double routingMissDistanceBulk =  stats.routingMissDistanceBulk.currentValue();
        double routingMissDistanceRT =  stats.routingMissDistanceRT.currentValue();
        double backedOffPercent =  stats.backedOffPercent.currentValue();
        String nodeUptimeString = TimeUtil.formatTime(MILLISECONDS.convert(nodeUptimeSeconds, SECONDS));

        // BEGIN OVERVIEW TABLE
        HTMLNode overviewTable = contentNode.addChild("table", "class", "column");
        HTMLNode overviewTableRow = overviewTable.addChild("tr");
        HTMLNode nextTableCell = overviewTableRow.addChild("td", "class", "first");
       
        HTMLNode overviewInfobox = nextTableCell.addChild("div", "class", "infobox");
        overviewInfobox.addChild("div", "class", "infobox-header", "Node status overview");
        HTMLNode overviewInfoboxContent = overviewInfobox.addChild("div", "class", "infobox-content");
        HTMLNode overviewList = overviewInfoboxContent.addChild("ul");
        overviewList.addChild("li", "bwlimitDelayTime:\u00a0" + bwlimitDelayTime + "ms");
        overviewList.addChild("li", "nodeAveragePingTime:\u00a0" + nodeAveragePingTime + "ms");
        overviewList.addChild("li", "darknetSizeEstimateSession:\u00a0" + networkSizeEstimateSession + "\u00a0nodes");
        if(nodeUptimeSeconds > HOURS.toSeconds(48)) {
          overviewList.addChild("li", "darknetSizeEstimateRecent:\u00a0" + networkSizeEstimateRecent + "\u00a0nodes");
        }
        overviewList.addChild("li", "nodeUptime:\u00a0" + nodeUptimeString);
        overviewList.addChild("li", "routingMissDistanceLocal:\u00a0" + fix4.format(routingMissDistanceLocal));
        overviewList.addChild("li", "routingMissDistanceRemote:\u00a0" + fix4.format(routingMissDistanceRemote));
        overviewList.addChild("li", "routingMissDistanceOverall:\u00a0" + fix4.format(routingMissDistanceOverall));
        overviewList.addChild("li", "routingMissDistanceBulk:\u00a0" + fix4.format(routingMissDistanceBulk));
        overviewList.addChild("li", "routingMissDistanceRT:\u00a0" + fix4.format(routingMissDistanceRT));
        overviewList.addChild("li", "backedOffPercent:\u00a0" + fix1.format(backedOffPercent));
        overviewList.addChild("li", "pInstantReject:\u00a0" + fix1.format(stats.pRejectIncomingInstantly()));
        nextTableCell = overviewTableRow.addChild("td");
       
        // Activity box
        int numARKFetchers = node.getNumARKFetchers();
       
        HTMLNode activityInfobox = nextTableCell.addChild("div", "class", "infobox");
        activityInfobox.addChild("div", "class", "infobox-header", l10n("activityTitle"));
        HTMLNode activityInfoboxContent = activityInfobox.addChild("div", "class", "infobox-content");
        HTMLNode activityList = StatisticsToadlet.drawActivity(activityInfoboxContent, node);
        if (advancedMode && (activityList != null)) {
          if (numARKFetchers > 0) {
            activityList.addChild("li", "ARK\u00a0Fetch\u00a0Requests:\u00a0" + numARKFetchers);
          }
          StatisticsToadlet.drawBandwidth(activityList, node, nodeUptimeSeconds, advancedMode);
        }
       
        nextTableCell = overviewTableRow.addChild("td", "class", "last");
       
        // Peer statistics box
        HTMLNode peerStatsInfobox = nextTableCell.addChild("div", "class", "infobox");
        StatisticsToadlet.drawPeerStatsBox(peerStatsInfobox, advancedMode, numberOfConnected, numberOfRoutingBackedOff, numberOfTooNew, numberOfTooOld, numberOfDisconnected, numberOfNeverConnected, numberOfDisabled, numberOfBursting, numberOfListening, numberOfListenOnly, 0, 0, numberOfRoutingDisabled, numberOfClockProblem, numberOfConnError, numberOfDisconnecting, numberOfNoLoadStats, node);
       
        // Peer routing backoff reason box
        if(advancedMode) {
          HTMLNode backoffReasonInfobox = nextTableCell.addChild("div", "class", "infobox");
          HTMLNode title = backoffReasonInfobox.addChild("div", "class", "infobox-header", "Peer backoff reasons (realtime)");
          HTMLNode backoffReasonContent = backoffReasonInfobox.addChild("div", "class", "infobox-content");
          String [] routingBackoffReasons = peers.getPeerNodeRoutingBackoffReasons(true);
          int total = 0;
          if(routingBackoffReasons.length == 0) {
            backoffReasonContent.addChild("#", NodeL10n.getBase().getString("StatisticsToadlet.notBackedOff"));
          } else {
            HTMLNode reasonList = backoffReasonContent.addChild("ul");
            for(String routingBackoffReason: routingBackoffReasons) {
              int reasonCount = peers.getPeerNodeRoutingBackoffReasonSize(routingBackoffReason, true);
              if(reasonCount > 0) {
                total += reasonCount;
                reasonList.addChild("li", routingBackoffReason + '\u00a0' + reasonCount);
              }
            }
          }
          if(total > 0)
            title.addChild("#", ": "+total);
          backoffReasonInfobox = nextTableCell.addChild("div", "class", "infobox");
          title = backoffReasonInfobox.addChild("div", "class", "infobox-header", "Peer backoff reasons (bulk)");
          backoffReasonContent = backoffReasonInfobox.addChild("div", "class", "infobox-content");
          routingBackoffReasons = peers.getPeerNodeRoutingBackoffReasons(false);
          total = 0;
          if(routingBackoffReasons.length == 0) {
            backoffReasonContent.addChild("#", NodeL10n.getBase().getString("StatisticsToadlet.notBackedOff"));
          } else {
            HTMLNode reasonList = backoffReasonContent.addChild("ul");
            for(String routingBackoffReason: routingBackoffReasons) {
              int reasonCount = peers.getPeerNodeRoutingBackoffReasonSize(routingBackoffReason, false);
              if(reasonCount > 0) {
                total += reasonCount;
                reasonList.addChild("li", routingBackoffReason + '\u00a0' + reasonCount);
              }
            }
          }
          if(total > 0)
            title.addChild("#", ": "+total);
        }
        // END OVERVIEW TABLE
      }
     
      boolean enablePeerActions = showPeerActionsBox();
     
      // BEGIN PEER TABLE
      if(fProxyJavascriptEnabled) {
        StringBuilder jsBuf = new StringBuilder();
        // FIXME: There's probably some icky Javascript in here (this is the first thing that worked for me); feel free to fix up to Javascript guru standards
        jsBuf.append( "  function peerNoteChange() {\n" );
        jsBuf.append( "    var theobj = document.getElementById( \"action\" );\n" );
        jsBuf.append( "    var length = theobj.options.length;\n" );
        jsBuf.append( "    for (var i = 0; i < length; i++) {\n" );
        jsBuf.append( "      if(theobj.options[i] == \"update_notes\") {\n" );
        jsBuf.append( "        theobj.options[i].select = true;\n" );
        jsBuf.append( "      } else {\n" );
        jsBuf.append( "        theobj.options[i].select = false;\n" );
        jsBuf.append( "      }\n" );
        jsBuf.append( "    }\n" );
        jsBuf.append( "    theobj.value=\"update_notes\";\n" );
        //jsBuf.append( "    document.getElementById( \"peersForm\" ).submit();\n" );
        jsBuf.append( "    document.getElementById( \"peersForm\" ).doAction.click();\n" );
        jsBuf.append( "  }\n" );
        jsBuf.append( "  function peerNoteBlur() {\n" );
        jsBuf.append( "    var theobj = document.getElementById( \"action\" );\n" );
        jsBuf.append( "    var length = theobj.options.length;\n" );
        jsBuf.append( "    for (var i = 0; i < length; i++) {\n" );
        jsBuf.append( "      if(theobj.options[i] == \"update_notes\") {\n" );
        jsBuf.append( "        theobj.options[i].select = true;\n" );
        jsBuf.append( "      } else {\n" );
        jsBuf.append( "        theobj.options[i].select = false;\n" );
        jsBuf.append( "      }\n" );
        jsBuf.append( "    }\n" );
        jsBuf.append( "    theobj.value=\"update_notes\";\n" );
        jsBuf.append( "  }\n" );
        contentNode.addChild("script", "type", "text/javascript").addChild("%", jsBuf.toString());
      }
      HTMLNode peerTableInfobox = contentNode.addChild("div", "class", "infobox infobox-normal");
      HTMLNode peerTableInfoboxHeader = peerTableInfobox.addChild("div", "class", "infobox-header");
      peerTableInfoboxHeader.addChild("#", getPeerListTitle());
      if (advancedMode) {
        if (!path.endsWith("displaymessagetypes.html")) {
          peerTableInfoboxHeader.addChild("#", " ");
          peerTableInfoboxHeader.addChild("a", "href", "displaymessagetypes.html", l10n("bracketedMoreDetailed"));
        }
      }
      HTMLNode peerTableInfoboxContent = peerTableInfobox.addChild("div", "class", "infobox-content");

      if (!isOpennet()) {
        HTMLNode myName = peerTableInfoboxContent.addChild("p");
        myName.addChild("span",
            NodeL10n.getBase().getString("DarknetConnectionsToadlet.myName", "name", node.getMyName()));
        myName.addChild("span", " [");
        myName.addChild("span").addChild("a", "href", "/config/node#name",
            NodeL10n.getBase().getString("DarknetConnectionsToadlet.changeMyName"));
        myName.addChild("span", "]");
      }

      if (peerNodeStatuses.length == 0) {
        NodeL10n.getBase().addL10nSubstitution(peerTableInfoboxContent, "DarknetConnectionsToadlet.noPeersWithHomepageLink",
            new String[] { "link" }, new HTMLNode[] { HTMLNode.link("/") });
      } else {
        HTMLNode peerForm = null;
        HTMLNode peerTable;
        if(enablePeerActions) {
          peerForm = ctx.addFormChild(peerTableInfoboxContent, ".", "peersForm");
          peerTable = peerForm.addChild("table", "class", "darknet_connections");
        } else {
          peerTable = peerTableInfoboxContent.addChild("table", "class", "darknet_connections");
        }
        HTMLNode peerTableHeaderRow = peerTable.addChild("tr");
        if(enablePeerActions)
          peerTableHeaderRow.addChild("th");
        peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "status")).addChild("#", l10n("statusTitle"));
        if(hasNameColumn())
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "name")).addChild("span", new String[] { "title", "style" }, new String[] { l10n("nameClickToMessage"), "border-bottom: 1px dotted; cursor: help;" }, l10n("nameTitle"));
        if(hasTrustColumn())
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "trust")).addChild("span", new String[] { "title", "style" }, new String[] { l10n("trustMessage"), "border-bottom: 1px dotted; cursor: help;" }, l10n("trustTitle"));
        if(hasVisibilityColumn())
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "trust")).addChild("span", new String[] { "title", "style" }, new String[] { l10n("visibilityMessage"+(advancedMode?"Advanced":"Simple")), "border-bottom: 1px dotted; cursor: help;" }, l10n("visibilityTitle"));
        peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "address")).addChild("span", new String[] { "title", "style" }, new String[] { l10n("ipAddress"), "border-bottom: 1px dotted; cursor: help;" }, l10n("ipAddressTitle"));
        peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "version")).addChild("#", l10n("versionTitle"));
        if (advancedMode) {
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "location")).addChild("#", l10n("locationTitle"));
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "backoffRT")).addChild("span", new String[] { "title", "style" }, new String[] { "Other node busy (realtime)? Display: Percentage of time the node is overloaded, Current wait time remaining (0=not overloaded)/total/last overload reason", "border-bottom: 1px dotted; cursor: help;" }, "Backoff (realtime)");
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "backoffBulk")).addChild("span", new String[] { "title", "style" }, new String[] { "Other node busy (bulk)? Display: Percentage of time the node is overloaded, Current wait time remaining (0=not overloaded)/total/last overload reason", "border-bottom: 1px dotted; cursor: help;" }, "Backoff (bulk)");

          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "overload_p")).addChild("span", new String[] { "title", "style" }, new String[] { "Probability of the node rejecting a request due to overload or causing a timeout.", "border-bottom: 1px dotted; cursor: help;" }, "Overload Probability");
        }
        peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "idle")).addChild("span", new String[] { "title", "style" }, new String[] { l10n("idleTime"), "border-bottom: 1px dotted; cursor: help;" }, l10n("idleTimeTitle"));
        if(hasPrivateNoteColumn())
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "privnote")).addChild("span", new String[] { "title", "style" }, new String[] { l10n("privateNote"), "border-bottom: 1px dotted; cursor: help;" }, l10n("privateNoteTitle"));
        if(advancedMode) {
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "time_routable")).addChild("#", "%\u00a0Time Routable");
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "selection_percentage")).addChild("#", "%\u00a0Selection");
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "total_traffic")).addChild("#", "Total\u00a0Traffic\u00a0(in/out/resent)");
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "total_traffic_since_startup")).addChild("#", "Total\u00a0Traffic\u00a0(in/out) since startup");
          peerTableHeaderRow.addChild("th", "Congestion\u00a0Control");
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "time_delta")).addChild("#", "Time\u00a0Delta");
          peerTableHeaderRow.addChild("th").addChild("a", "href", sortString(isReversed, "uptime")).addChild("#", "Reported\u00a0Uptime");
          peerTableHeaderRow.addChild("th", "Transmit\u00a0Queue");
          peerTableHeaderRow.addChild("th", "Peer\u00a0Capacity\u00a0Bulk");
          peerTableHeaderRow.addChild("th", "Peer\u00a0Capacity\u00a0Realtime");
        }
       
        SimpleColumn[] endCols = endColumnHeaders(advancedMode);
        if(endCols != null) {
          for(SimpleColumn col: endCols) {
            HTMLNode header = peerTableHeaderRow.addChild("th");
            String sortString = col.getSortString();
            if(sortString != null)
              header = header.addChild("a", "href", sortString(isReversed, sortString));
            header.addChild("span", new String[] { "title", "style" }, new String[] { NodeL10n.getBase().getString(col.getExplanationKey()), "border-bottom: 1px dotted; cursor: help;" }, NodeL10n.getBase().getString(col.getTitleKey()));
          }
        }

        double totalSelectionRate = 0.0;
        //calculate the total selection rate using all peers, not just the peers for the current mode,
        PeerNodeStatus[] allPeerNodeStatuses = node.peers.getPeerNodeStatuses(true);
        for(PeerNodeStatus status : allPeerNodeStatuses) {
          totalSelectionRate += status.getSelectionRate();
        }
        for (PeerNodeStatus peerNodeStatus: peerNodeStatuses) {
          drawRow(peerTable, peerNodeStatus, advancedMode, fProxyJavascriptEnabled, now, path, enablePeerActions, endCols, drawMessageTypes, totalSelectionRate, fix1);
        }

        if(peerForm != null) {
          drawPeerActionSelectBox(peerForm, advancedMode);
        }
      }
      // END PEER TABLE

      // FOAF locations table.
      if(advancedMode) {
        //requires a location-to-list/count in-memory transform
        List<Double> locations=new ArrayList<Double>();
        List<List<PeerNodeStatus>> peerGroups=new ArrayList<List<PeerNodeStatus>>();
        {
          for (PeerNodeStatus peerNodeStatus : peerNodeStatuses) {
            double[] peersLoc = peerNodeStatus.getPeersLocation();
            if (peersLoc!=null) {
              for (double location : peersLoc) {
                int i;
                int max=locations.size();
                // FIXME Fix O(n^2): Use Arrays.binarySearch or use a TreeMap.
                for (i=0; i<max && locations.get(i)<location; i++);
                //i now points to the proper location (equal, insertion point, or end-of-list)
                //maybe better called "reverseGroup"?
                List<PeerNodeStatus> peerGroup;
                if (i<max && locations.get(i).doubleValue()==location) {
                  peerGroup=peerGroups.get(i);
                } else {
                  peerGroup=new ArrayList<PeerNodeStatus>();
                  locations.add(i, location);
                  peerGroups.add(i, peerGroup);
                }
                peerGroup.add(peerNodeStatus);
              }
            }
          }
        }
        //transform complete.... now we have peers listed by foaf's ordered by ascending location
        int trivialCount=0;
        int nonTrivialCount=0;
        int transitiveCount=0;
        for (List<PeerNodeStatus> list : peerGroups) {
          if (list.size()==1)
            trivialCount++;
          else
            nonTrivialCount++;
        }
        peerTableInfoboxContent.addChild("b", l10n("secondDegreeConnectionsCountTitle", "count", Integer.toString(locations.size())));
        peerTableInfoboxContent.addChild("br");
        if (!showTrivialFoafConnections) {
          peerTableInfoboxContent.addChild("i", l10n("secondDegreeTrivialHiddenCount", "count", Integer.toString(trivialCount)));
          //@todo: add "show these" link
        } else {
          peerTableInfoboxContent.addChild("i", l10n("secondDegreeNonTrivialCount", "count", Integer.toString(nonTrivialCount)));
          //@todo: add "hide these" link
        }
        HTMLNode foafTable = peerTableInfoboxContent.addChild("table", "class", "darknet_connections"); //@todo: change css class?
        HTMLNode foafRow = foafTable.addChild("tr");
        {
          foafRow.addChild("th", l10n("locationTitle"));
          foafRow.addChild("th", l10n("countTitle"));
          foafRow.addChild("th", l10n("foafReachableThroughTitle"));
        }
        int max=locations.size();
        for (int i=0; i<max; i++) {
          double location=locations.get(i);
          List<PeerNodeStatus> peersWithFriend=peerGroups.get(i);
          boolean isTransitivePeer=false;
          {
            for (PeerNodeStatus peerNodeStatus : peerNodeStatuses) {
              if (location==peerNodeStatus.getLocation()) {
                isTransitivePeer=true;
                transitiveCount++;
                break;
              }
            }
          }
          if (peersWithFriend.size()==1 && !showTrivialFoafConnections && !isTransitivePeer)
            continue;
          foafRow=foafTable.addChild("tr");
          {
            if (isTransitivePeer) {
              foafRow.addChild("td").addChild("b", String.valueOf(location));
            } else {
              foafRow.addChild("td", String.valueOf(location));
            }
            foafRow.addChild("td", String.valueOf(peersWithFriend.size()));
            HTMLNode locationCell=foafRow.addChild("td", "class", "peer-location");
            for (PeerNodeStatus peerNodeStatus : peersWithFriend) {
              String address=((peerNodeStatus.getPeerAddress() != null) ? (peerNodeStatus.getPeerAddress() + ':' + peerNodeStatus.getPeerPort()) : (l10n("unknownAddress")));
              locationCell.addChild("i", address);
              locationCell.addChild("br");
            }
          }
        }
        if (transitiveCount>0) {
          peerTableInfoboxContent.addChild("i", l10n("secondDegreeAlsoOurs", "count", Integer.toString(transitiveCount)));
        }
      }
      // END FOAF TABLE

    } else {
      if(!isOpennet()) {
        try {
          throw new RedirectException("/addfriend/");
        } catch (URISyntaxException e) {
          Logger.error(this, "Impossible: "+e+" for /addfriend/", e);
        }
      }
    }
   
    // our reference
    if(shouldDrawNoderefBox(advancedMode)) {
      drawAddPeerBox(contentNode, ctx);
      drawNoderefBox(contentNode, getNoderef(), true);
    }
   
    this.writeHTMLReply(ctx, 200, "OK", pageNode.generate());
  }

  protected abstract boolean acceptRefPosts();
 
  /** Where to redirect to if there is an error */
  protected abstract String defaultRedirectLocation();

  public void handleMethodPOST(URI uri, final HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException, RedirectException {
    boolean logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
   
    if(!acceptRefPosts()) {
        sendUnauthorizedPage(ctx);
      return;
    }
   
        if(!ctx.checkFullAccess(this))
            return;
       
    if (request.isPartSet("add")) {
      // add a new node
      String urltext = request.getPartAsStringFailsafe("url", 200);
      urltext = urltext.trim();
      String reftext = request.getPartAsStringFailsafe("ref", Integer.MAX_VALUE);
      reftext = reftext.trim();
      if (reftext.length() < 200) {
        reftext = request.getPartAsStringFailsafe("reffile", Integer.MAX_VALUE);
        reftext = reftext.trim();
      }
      String privateComment = null;
      if(!isOpennet())
        privateComment = request.getPartAsStringFailsafe("peerPrivateNote", 250).trim();
     
      String trustS = request.getPartAsStringFailsafe("trust", 10);
      FRIEND_TRUST trust = null;
      if(trustS != null && !trustS.equals(""))
        trust = FRIEND_TRUST.valueOf(trustS);
     
      String visibilityS = request.getPartAsStringFailsafe("visibility", 10);
      FRIEND_VISIBILITY visibility = null;
      if(visibilityS != null && !visibilityS.equals(""))
        visibility = FRIEND_VISIBILITY.valueOf(visibilityS);
     
      if(trust == null && !isOpennet()) {
        // FIXME: Layering violation. Ideally DarknetPeerNode would do this check.
        this.sendErrorPage(ctx, 200, l10n("noTrustLevelAddingFriendTitle"), l10n("noTrustLevelAddingFriend"), !isOpennet());
        return;
      }
     
      if(visibility == null && !isOpennet()) {
        // FIXME: Layering violation. Ideally DarknetPeerNode would do this check.
        this.sendErrorPage(ctx, 200, l10n("noVisibilityLevelAddingFriendTitle"), l10n("noVisibilityLevelAddingFriend"), !isOpennet());
        return;
      }
     
      StringBuilder ref = null;
      if (urltext.length() > 0) {
        // fetch reference from a URL
        BufferedReader in = null;
        try {
          URL url = new URL(urltext);
          ref = AddPeer.getReferenceFromURL(url);
        } catch (IOException e) {
          this.sendErrorPage(ctx, 200, l10n("failedToAddNodeTitle"), NodeL10n.getBase().getString("DarknetConnectionsToadlet.cantFetchNoderefURL", new String[] { "url" }, new String[] { urltext }), !isOpennet());
          return;
        } finally {
          Closer.close(in);
        }
      } else if (reftext.length() > 0) {
        // read from post data or file upload
        // this slightly scary looking regexp chops any extra characters off the beginning or ends of lines and removes extra line breaks
        ref = new StringBuilder(reftext.replaceAll(".*?((?:[\\w,\\.]+\\=[^\r\n]+?)|(?:End))[ \\t]*(?:\\r?\\n)+", "$1\n"));
      } else {
        this.sendErrorPage(ctx, 200, l10n("failedToAddNodeTitle"), l10n("noRefOrURL"), !isOpennet());
        request.freeParts();
        return;
      }
      ref = new StringBuilder(ref.toString().trim());

      request.freeParts();

      //Split the references string, because the peers are added individually
      // FIXME split by lines at this point rather than in addNewNode would be more efficient
      int idx;
      while((idx = ref.indexOf("\r\n")) > -1) {
        ref.deleteCharAt(idx);
      }
      while((idx = ref.indexOf("\r")) > -1) {
        // Mac's just use \r
        ref.setCharAt(idx, '\n');
      }
      String[] nodesToAdd=ref.toString().split("\nEnd\n");
      for(int i=0;i<nodesToAdd.length;i++) {
        String[] split = nodesToAdd[i].split("\n");
        StringBuffer sb = new StringBuffer(nodesToAdd[i].length());
        boolean first = true;
        for(String s : split) {
          if(s.equals("End")) break;
          if(s.indexOf('=') > -1) {
            if(!first)
              sb.append('\n');
          } else {
            // Try appending it - don't add a newline.
            // This will make broken refs work sometimes.
          }
          sb.append(s);
          first = false;
        }
        nodesToAdd[i] = sb.toString();
        // Don't need to add a newline at the end, we will do that later.
      }
      //The peer's additions results
      Map<PeerAdditionReturnCodes,Integer> results=new HashMap<PeerAdditionReturnCodes, Integer>();
      for(int i=0;i<nodesToAdd.length;i++){
        //We need to trim then concat 'End' to the node's reference, this way we have a normal reference(the split() removes the 'End'-s!)
        PeerAdditionReturnCodes result=addNewNode(nodesToAdd[i].trim().concat("\nEnd"), privateComment, trust, visibility);
        //Store the result
        Integer prev = results.get(result);
        if(prev == null) prev = Integer.valueOf(0);
        results.put(result, prev+1);
      }
     
      PageNode page = ctx.getPageMaker().getPageNode(l10n("reportOfNodeAddition"), ctx);
      HTMLNode pageNode = page.outer;
      HTMLNode contentNode = page.content;
     
      //We create a table to show the results
      HTMLNode detailedStatusBox=new HTMLNode("table");
      //Header of the table
      detailedStatusBox.addChild(new HTMLNode("tr")).addChildren(new HTMLNode[]{new HTMLNode("th",l10n("resultName")),new HTMLNode("th",l10n("numOfResults"))});
      HTMLNode statusBoxTable=detailedStatusBox.addChild(new HTMLNode("tbody"));
      //Iterate through the return codes
      for(PeerAdditionReturnCodes returnCode:PeerAdditionReturnCodes.values()){
        if(results.containsKey(returnCode)){
          //Add a <tr> and 2 <td> with the name of the code and the number of occasions it happened. If the code is OK, we use green, red elsewhere.
          statusBoxTable.addChild(new HTMLNode("tr","style","color:"+(returnCode==PeerAdditionReturnCodes.OK?"green":"red"))).addChildren(new HTMLNode[]{new HTMLNode("td",l10n("peerAdditionCode."+returnCode.toString())),new HTMLNode("td",results.get(returnCode).toString())});
        }
      }

      HTMLNode infoboxContent = ctx.getPageMaker().getInfobox("infobox",l10n("reportOfNodeAddition"), contentNode, "node-added", true);
      infoboxContent.addChild(detailedStatusBox);
      if(!isOpennet())
        infoboxContent.addChild("p").addChild("a", "href", "/addfriend/", l10n("addAnotherFriend"));
      infoboxContent.addChild("p").addChild("a", "href", path(), l10n("goFriendConnectionStatus"));
      addHomepageLink(infoboxContent.addChild("p"));
     
      writeHTMLReply(ctx, 500, l10n("reportOfNodeAddition"), pageNode.generate());
    } else handleAltPost(uri, request, ctx, logMINOR);
   
   
  }
 
  /** Adds a new node. If any error arises, it returns the appropriate return code.
   * @param nodeReference - The reference to the new node
   * @param privateComment - The private comment when adding a Darknet node
   * @param trust
   * @param request To pull any extra fields from
   * @return The result of the addition*/
  private PeerAdditionReturnCodes addNewNode(String nodeReference,String privateComment, FRIEND_TRUST trust, FRIEND_VISIBILITY visibility){
    SimpleFieldSet fs;
   
    try {
      nodeReference = Fields.trimLines(nodeReference);
      fs = new SimpleFieldSet(nodeReference, false, true, true);
      if(!fs.getEndMarker().endsWith("End")) {
        Logger.error(this, "Trying to add noderef with end marker \""+fs.getEndMarker()+"\"");
        return PeerAdditionReturnCodes.WRONG_ENCODING;
      }
      fs.setEndMarker("End"); // It's always End ; the regex above doesn't always grok this
    } catch (IOException e) {
            Logger.error(this, "IOException adding reference :" + e.getMessage(), e);
      return PeerAdditionReturnCodes.CANT_PARSE;
    } catch (Throwable t) {
        Logger.error(this, "Internal error adding reference :" + t.getMessage(), t);
      return PeerAdditionReturnCodes.INTERNAL_ERROR;
    }
    PeerNode pn;
    try {
      if(isOpennet()) {
        pn = node.createNewOpennetNode(fs);
      } else {
        pn = node.createNewDarknetNode(fs, trust, visibility);
        ((DarknetPeerNode)pn).setPrivateDarknetCommentNote(privateComment);
      }
    } catch (FSParseException e1) {
      return PeerAdditionReturnCodes.CANT_PARSE;
    } catch (PeerParseException e1) {
      return PeerAdditionReturnCodes.CANT_PARSE;
    } catch (ReferenceSignatureVerificationException e1){
      return PeerAdditionReturnCodes.INVALID_SIGNATURE;
    } catch (Throwable t) {
            Logger.error(this, "Internal error adding reference :" + t.getMessage(), t);
      return PeerAdditionReturnCodes.INTERNAL_ERROR;
    }
    if(Arrays.equals(pn.getPubKeyHash(), node.getDarknetPubKeyHash())) {
      return PeerAdditionReturnCodes.TRY_TO_ADD_SELF;
    }
    if(!this.node.addPeerConnection(pn)) {
      return PeerAdditionReturnCodes.ALREADY_IN_REFERENCE;
    }
    return PeerAdditionReturnCodes.OK;
  }

  /** Adding a darknet node or an opennet node? */
  protected abstract boolean isOpennet();

  /**
   * Rest of handlePost() method - supplied by subclass.
   * @throws IOException
   * @throws ToadletContextClosedException
   * @throws RedirectException
   */
  protected void handleAltPost(URI uri, HTTPRequest request, ToadletContext ctx, boolean logMINOR) throws ToadletContextClosedException, IOException, RedirectException {
    // Do nothing - we only support adding nodes
    handleMethodGET(uri, new HTTPRequestImpl(uri, "GET"), ctx);
  }

  /**
   * What should the heading (before "(more detailed)") be on the peers table?
   */
  protected abstract String getPeerListTitle();

  /** Should there be a checkbox for each peer, and drawPeerActionSelectBox() be called directly
   * after drawing the peers list? */
  protected abstract boolean showPeerActionsBox();

  /** If showPeerActionsBox() is true, this will be called directly after drawing the peers table.
   * A form has been added, and checkboxes added for each peer. This function should draw the rest
   * of the form - any additional controls and one or more submit buttons.
   */
  protected abstract void drawPeerActionSelectBox(HTMLNode peerForm, boolean advancedModeEnabled);
 
  protected abstract boolean shouldDrawNoderefBox(boolean advancedModeEnabled);

  final HTMLNode REF_LINK;
  final HTMLNode REFTEXT_LINK;

  /**
   *
   * @param contentNode Node to add noderef box to.
   * @param fs Noderef to render as text if requested.
   * @param showNoderef If true, render the text of the noderef so that it may be copy-pasted. If false, only
   *                    show a link to download it.
   */
  void drawNoderefBox(HTMLNode contentNode, SimpleFieldSet fs, boolean showNoderef) {
    HTMLNode referenceInfobox = contentNode.addChild("div", "class", "infobox infobox-normal");
    HTMLNode headerReferenceInfobox = referenceInfobox.addChild("div", "class", "infobox-header");
    // FIXME better way to deal with this sort of thing???
    NodeL10n.getBase().addL10nSubstitution(headerReferenceInfobox, "DarknetConnectionsToadlet.myReferenceHeader",
        new String[] { "linkref", "linktext" },
        new HTMLNode[] { REF_LINK, REFTEXT_LINK });
    HTMLNode referenceInfoboxContent = referenceInfobox.addChild("div", "class", "infobox-content");
   
    if(!isOpennet()) {
      HTMLNode myName = referenceInfoboxContent.addChild("p");
      myName.addChild("span",
          NodeL10n.getBase().getString("DarknetConnectionsToadlet.myName", "name", fs.get("myName")));
      myName.addChild("span", " [");
      myName.addChild("span").addChild("a", "href", "/config/node#name",
          NodeL10n.getBase().getString("DarknetConnectionsToadlet.changeMyName"));
      myName.addChild("span", "]");
    }

    if (showNoderef) {
      HTMLNode warningSentence = referenceInfoboxContent.addChild("p");
      NodeL10n.getBase().addL10nSubstitution(warningSentence, "DarknetConnectionsToadlet.referenceCopyWarning",
          new String[] { "bold" },
          new HTMLNode[] { HTMLNode.STRONG });
      referenceInfoboxContent.addChild("pre", "id", "reference", fs.toOrderedStringWithBase64() + '\n');
    }
  }

  protected abstract String getPageTitle(String titleCountString);

  /** Draw the add a peer box. This comes immediately after the main peers table and before the noderef box.
   * Implementors may skip it by not doing anything in this method. */
  protected void drawAddPeerBox(HTMLNode contentNode, ToadletContext ctx) {
    drawAddPeerBox(contentNode, ctx, isOpennet(), path());
  }
 
  protected static void drawAddPeerBox(HTMLNode contentNode, ToadletContext ctx, boolean isOpennet, String formTarget) {
    // BEGIN PEER ADDITION BOX
    HTMLNode peerAdditionInfobox = contentNode.addChild("div", "class", "infobox infobox-normal");
    peerAdditionInfobox.addChild("div", "class", "infobox-header", l10n(isOpennet ? "addOpennetPeerTitle" : "addPeerTitle"));
    HTMLNode peerAdditionContent = peerAdditionInfobox.addChild("div", "class", "infobox-content");
    HTMLNode peerAdditionForm = ctx.addFormChild(peerAdditionContent, formTarget, "addPeerForm");
    peerAdditionForm.addChild("#", l10n("pasteReference"));
    peerAdditionForm.addChild("br");
    peerAdditionForm.addChild("textarea", new String[] { "id", "name", "rows", "cols" }, new String[] { "reftext", "ref", "8", "74" });
    peerAdditionForm.addChild("br");
    peerAdditionForm.addChild("#", (l10n("urlReference") + ' '));
    peerAdditionForm.addChild("input", new String[] { "id", "type", "name" }, new String[] { "refurl", "text", "url" });
    peerAdditionForm.addChild("br");
    peerAdditionForm.addChild("#", (l10n("fileReference") + ' '));
    peerAdditionForm.addChild("input", new String[] { "id", "type", "name" }, new String[] { "reffile", "file", "reffile" });
    peerAdditionForm.addChild("br");
    if(!isOpennet) {
      peerAdditionForm.addChild("b", l10n("peerTrustTitle"));
      peerAdditionForm.addChild("#", " ");
      peerAdditionForm.addChild("#", l10n("peerTrustIntroduction"));
      for(FRIEND_TRUST trust : FRIEND_TRUST.valuesBackwards()) { // FIXME reverse order
        HTMLNode input = peerAdditionForm.addChild("br").addChild("input", new String[] { "type", "name", "value" }, new String[] { "radio", "trust", trust.name() });
        if (trust.isDefaultValue()) {
          input.addAttribute("checked", "checked");
        }
        input.addChild("b", l10n("peerTrust."+trust.name())); // FIXME l10n
        input.addChild("#", ": ");
        input.addChild("#", l10n("peerTrustExplain."+trust.name()));
      }
      peerAdditionForm.addChild("br");
     
      peerAdditionForm.addChild("b", l10n("peerVisibilityTitle"));
      peerAdditionForm.addChild("#", " ");
      peerAdditionForm.addChild("#", l10n("peerVisibilityIntroduction"));
      for(FRIEND_VISIBILITY visibility : FRIEND_VISIBILITY.values()) { // FIXME reverse order
        HTMLNode input = peerAdditionForm.addChild("br").addChild("input", new String[] { "type", "name", "value" }, new String[] { "radio", "visibility", visibility.name() });
        if (visibility.isDefaultValue()) {
          input.addAttribute("checked", "checked");
        }
        input.addChild("b", l10n("peerVisibility."+visibility.name())); // FIXME l10n
        input.addChild("#", ": ");
        input.addChild("#", l10n("peerVisibilityExplain."+visibility.name()));
      }
      peerAdditionForm.addChild("br");
     
    }
   
    if(!isOpennet) {
      peerAdditionForm.addChild("#", (l10n("enterDescription") + ' '));
      peerAdditionForm.addChild("input", new String[] { "id", "type", "name", "size", "maxlength", "value" }, new String[] { "peerPrivateNote", "text", "peerPrivateNote", "16", "250", "" });
      peerAdditionForm.addChild("br");
    }
    peerAdditionForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "add", l10n("add") });
  }

  protected Comparator<PeerNodeStatus> comparator(String sortBy, boolean reversed) {
    return new ComparatorByStatus(sortBy, reversed);
  }

  abstract protected PeerNodeStatus[] getPeerNodeStatuses(boolean noHeavy);

  abstract protected SimpleFieldSet getNoderef();

  private void drawRow(HTMLNode peerTable, PeerNodeStatus peerNodeStatus, boolean advancedModeEnabled, boolean fProxyJavascriptEnabled, long now, String path, boolean enablePeerActions, SimpleColumn[] endCols, boolean drawMessageTypes, double totalSelectionRate, DecimalFormat fix1) {
    double selectionRate = peerNodeStatus.getSelectionRate();
    int peerSelectionPercentage = 0;
    if(totalSelectionRate > 0)
      peerSelectionPercentage = (int) (selectionRate * 100 / totalSelectionRate);
    HTMLNode peerRow = peerTable.addChild("tr", "class", "darknet_connections_"+(peerSelectionPercentage > PeerNode.SELECTION_PERCENTAGE_WARNING ? "warning" : "normal"));
   
    if(enablePeerActions) {
      // check box column
      peerRow.addChild("td", "class", "peer-marker").addChild("input", new String[] { "type", "name" }, new String[] { "checkbox", "node_" + peerNodeStatus.hashCode() });
    }

    // status column
    String statusString = peerNodeStatus.getStatusName();
    if (!advancedModeEnabled && (peerNodeStatus.getStatusValue() == PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF)) {
      statusString = "BUSY";
    }
    /*
     * Some status names have spaces, but a space separates key and value if not immediately followed by '='.
     * Changing the names is not an option because they are exposed over FCP and TMCI.
     */
    final String key = "ConnectionsToadlet.nodeStatus." + statusString.replace(' ', '_');
    peerRow.addChild("td", "class", "peer-status").addChild("span", "class", peerNodeStatus.getStatusCSSName(), NodeL10n.getBase().getString(key) + (peerNodeStatus.isFetchingARK() ? "*" : ""));

    drawNameColumn(peerRow, peerNodeStatus, advancedModeEnabled);
   
    drawTrustColumn(peerRow, peerNodeStatus);
   
    drawVisibilityColumn(peerRow, peerNodeStatus, advancedModeEnabled);
   
    // address column
    String pingTime = "";
    if (peerNodeStatus.isConnected()) {
      pingTime = " (" + (int) peerNodeStatus.getAveragePingTime() + "ms / " +
      (int) peerNodeStatus.getAveragePingTimeCorrected()+"ms)";
    }
    HTMLNode addressRow = peerRow.addChild("td", "class", "peer-address");
    // Ip to country + Flags
    IPConverter ipc = IPConverter.getInstance(node.runDir().file(NodeUpdateManager.IPV4_TO_COUNTRY_FILENAME));
    byte[] addr = peerNodeStatus.getPeerAddressBytes();

    Country country = ipc.locateIP(addr);
    if(country != null) {
      country.renderFlagIcon(addressRow);
    }

    addressRow.addChild("#", ((peerNodeStatus.getPeerAddress() != null) ? (peerNodeStatus.getPeerAddress() + ':' + peerNodeStatus.getPeerPort()) : (l10n("unknownAddress"))) + pingTime);

    // version column
    if (peerNodeStatus.getStatusValue() != PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED && (peerNodeStatus.isPublicInvalidVersion() || peerNodeStatus.isPublicReverseInvalidVersion())) {  // Don't draw attention to a version problem if NEVER CONNECTED
      peerRow.addChild("td", "class", "peer-version").addChild("span", "class", "peer_version_problem", Integer.toString(peerNodeStatus.getSimpleVersion()));
    } else {
      peerRow.addChild("td", "class", "peer-version").addChild("#", Integer.toString(peerNodeStatus.getSimpleVersion()));
    }

    // location column
    if (advancedModeEnabled) {
      HTMLNode locationNode = peerRow.addChild("td", "class", "peer-location");
      locationNode.addChild("b", String.valueOf(peerNodeStatus.getLocation()));
      locationNode.addChild("br");
      double[] peersLoc = peerNodeStatus.getPeersLocation();
      if(peersLoc != null) {
        locationNode.addChild("i", "+"+(peersLoc.length)+" friends");
      }
    }

    if (advancedModeEnabled) {
      // backoff column
      HTMLNode backoffCell = peerRow.addChild("td", "class", "peer-backoff");
      backoffCell.addChild("#", fix1.format(peerNodeStatus.getBackedOffPercent(true)));
      int backoff = (int) (Math.max(peerNodeStatus.getRoutingBackedOffUntil(true) - now, 0));
      // Don't list the backoff as zero before it's actually zero
      if ((backoff > 0) && (backoff < 1000)) {
        backoff = 1000;
      }
      backoffCell.addChild("#", ' ' + String.valueOf(backoff / 1000) + '/' + String.valueOf(peerNodeStatus.getRoutingBackoffLength(true) / 1000));
      backoffCell.addChild("#", (peerNodeStatus.getLastBackoffReason(true) == null) ? "" : ('/' + (peerNodeStatus.getLastBackoffReason(true))));

      // backoff column
      backoffCell = peerRow.addChild("td", "class", "peer-backoff");
      backoffCell.addChild("#", fix1.format(peerNodeStatus.getBackedOffPercent(false)));
      backoff = (int) (Math.max(peerNodeStatus.getRoutingBackedOffUntil(false) - now, 0));
      // Don't list the backoff as zero before it's actually zero
      if ((backoff > 0) && (backoff < 1000)) {
        backoff = 1000;
      }
      backoffCell.addChild("#", ' ' + String.valueOf(backoff / 1000) + '/' + String.valueOf(peerNodeStatus.getRoutingBackoffLength(false) / 1000));
      backoffCell.addChild("#", (peerNodeStatus.getLastBackoffReason(false) == null) ? "" : ('/' + (peerNodeStatus.getLastBackoffReason(false))));

      // overload probability column
      HTMLNode pRejectCell = peerRow.addChild("td", "class", "peer-backoff"); // FIXME
      pRejectCell.addChild("#", fix1.format(peerNodeStatus.getPReject()));
    }

    // idle column
    long idle = peerNodeStatus.getTimeLastRoutable();
    if (peerNodeStatus.isRoutable()) {
      idle = peerNodeStatus.getTimeLastConnectionCompleted();
    } else if (peerNodeStatus.getStatusValue() == PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED) {
      idle = peerNodeStatus.getPeerAddedTime();
    }
    if(!peerNodeStatus.isConnected() && (now - idle) > (2 * 7 * 24 * 60 * 60 * (long) 1000)) { // 2 weeks
      peerRow.addChild("td", "class", "peer-idle").addChild("span", "class", "peer_idle_old", idleToString(now, idle));
    } else {
      peerRow.addChild("td", "class", "peer-idle", idleToString(now, idle));
    }

    if(hasPrivateNoteColumn())
      drawPrivateNoteColumn(peerRow, peerNodeStatus, fProxyJavascriptEnabled);

    if(advancedModeEnabled) {
      // percent of time connected column
      peerRow.addChild("td", "class", "peer-idle" /* FIXME */).addChild("#", fix1.format(peerNodeStatus.getPercentTimeRoutableConnection()));
      // selection stats
      peerRow.addChild("td", "class", "peer-idle" /* FIXME */).addChild("#", (totalSelectionRate > 0 ? (peerSelectionPercentage+"%") : "N/A"));
      // total traffic column
      long sent = peerNodeStatus.getTotalOutputBytes();
      long resent = peerNodeStatus.getResendBytesSent();
      long received = peerNodeStatus.getTotalInputBytes();
      peerRow.addChild("td", "class", "peer-idle" /* FIXME */).addChild("#", SizeUtil.formatSize(received)+" / "+SizeUtil.formatSize(sent)+"/"+SizeUtil.formatSize(resent)+" ("+fix1.format(((double)resent) / ((double)sent))+")");
      // total traffic column startup
      peerRow.addChild("td", "class", "peer-idle" /* FIXME */).addChild("#", SizeUtil.formatSize(peerNodeStatus.getTotalInputSinceStartup())+" / "+SizeUtil.formatSize(peerNodeStatus.getTotalOutputSinceStartup()));
      // congestion control
      PacketThrottle t = peerNodeStatus.getThrottle();
      String val;
      if(t == null)
        val = "none";
      else
        val = (int)t.getBandwidth()+"B/sec delay "+
          t.getDelay()+"ms (RTT "+t.getRoundTripTime()+"ms window "+t.getWindowSize()+')';
      peerRow.addChild("td", "class", "peer-idle" /* FIXME */).addChild("#", val);
      // time delta
      peerRow.addChild("td", "class", "peer-idle" /* FIXME */).addChild("#", TimeUtil.formatTime(peerNodeStatus.getClockDelta()));
      peerRow.addChild("td", "class", "peer-idle" /* FIXME */).addChild("#", peerNodeStatus.getReportedUptimePercentage()+"%");
      peerRow.addChild("td", "class", "peer-idle" /* FIXME */).addChild("#", SizeUtil.formatSize(peerNodeStatus.getMessageQueueLengthBytes())+":"+TimeUtil.formatTime(peerNodeStatus.getMessageQueueLengthTime()));
      IncomingLoadSummaryStats loadStatsBulk = peerNodeStatus.incomingLoadStatsBulk;
      if(loadStatsBulk == null)
        peerRow.addChild("td", "class", "peer-idle" /* FIXME */);
      else
        peerRow.addChild("td", "class", "peer-idle" /* FIXME */).addChild("#", loadStatsBulk.runningRequestsTotal+"reqs:out:"+SizeUtil.formatSize(loadStatsBulk.usedCapacityOutputBytes)+"/"+SizeUtil.formatSize(loadStatsBulk.othersUsedCapacityOutputBytes)+"/"+SizeUtil.formatSize(loadStatsBulk.peerCapacityOutputBytes)+"/"+SizeUtil.formatSize(loadStatsBulk.totalCapacityOutputBytes)+":in:"+SizeUtil.formatSize(loadStatsBulk.usedCapacityInputBytes)+"/"+SizeUtil.formatSize(loadStatsBulk.othersUsedCapacityInputBytes)+"/"+SizeUtil.formatSize(loadStatsBulk.peerCapacityInputBytes)+"/"+SizeUtil.formatSize(loadStatsBulk.totalCapacityInputBytes));
      IncomingLoadSummaryStats loadStatsRT = peerNodeStatus.incomingLoadStatsRealTime;
      if(loadStatsRT == null)
        peerRow.addChild("td", "class", "peer-idle" /* FIXME */);
      else
        peerRow.addChild("td", "class", "peer-idle" /* FIXME */).addChild("#", loadStatsRT.runningRequestsTotal+"reqs:out:"+SizeUtil.formatSize(loadStatsRT.usedCapacityOutputBytes)+"/"+SizeUtil.formatSize(loadStatsRT.othersUsedCapacityOutputBytes)+"/"+SizeUtil.formatSize(loadStatsRT.peerCapacityOutputBytes)+"/"+SizeUtil.formatSize(loadStatsRT.totalCapacityOutputBytes)+":in:"+SizeUtil.formatSize(loadStatsRT.usedCapacityInputBytes)+"/"+SizeUtil.formatSize(loadStatsRT.othersUsedCapacityInputBytes)+"/"+SizeUtil.formatSize(loadStatsRT.peerCapacityInputBytes)+"/"+SizeUtil.formatSize(loadStatsRT.totalCapacityInputBytes));
    }
   
    if(endCols != null) {
      for(SimpleColumn col: endCols) {
        col.drawColumn(peerRow, peerNodeStatus);
      }
    }
   
    if (drawMessageTypes) {
      drawMessageTypes(peerTable, peerNodeStatus);
    }
  }

  protected boolean hasTrustColumn() {
    return false;
  }

  protected void drawTrustColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus) {
    // Do nothing
  }

  protected boolean hasVisibilityColumn() {
    return false;
  }

  protected void drawVisibilityColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus, boolean advancedModeEnabled) {
    // Do nothing
  }

  /** Is there a name column? */
  abstract protected boolean hasNameColumn();
 
  /**
   * Draw the name column, if there is one. This will be directly after the status column.
   */
  abstract protected void drawNameColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus, boolean advanced);

  /**
   * Is there a private note column?
   */
  abstract protected boolean hasPrivateNoteColumn();

  /**
   * Draw the private note column.
   */
  abstract protected void drawPrivateNoteColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus, boolean fProxyJavascriptEnabled);
 
  private void drawMessageTypes(HTMLNode peerTable, PeerNodeStatus peerNodeStatus) {
    HTMLNode messageCountRow = peerTable.addChild("tr", "class", "message-status");
    messageCountRow.addChild("td", "colspan", "2");
    HTMLNode messageCountCell = messageCountRow.addChild("td", "colspan", "9")// = total table row width - 2 from above colspan
    HTMLNode messageCountTable = messageCountCell.addChild("table", "class", "message-count");
    HTMLNode countHeaderRow = messageCountTable.addChild("tr");
    countHeaderRow.addChild("th", "Message");
    countHeaderRow.addChild("th", "Incoming");
    countHeaderRow.addChild("th", "Outgoing");
    List<String> messageNames = new ArrayList<String>();
    Map<String, Long[]> messageCounts = new HashMap<String, Long[]>();
    for (Map.Entry<String,Long> entry : peerNodeStatus.getLocalMessagesReceived().entrySet() ) {
      String messageName = entry.getKey();
      Long messageCount = entry.getValue();
      messageNames.add(messageName);
      messageCounts.put(messageName, new Long[] { messageCount, Long.valueOf(0) });
    }
    for (Map.Entry<String,Long> entry : peerNodeStatus.getLocalMessagesSent().entrySet() ) {
      String messageName =  entry.getKey();
      Long messageCount = entry.getValue();
      if (!messageNames.contains(messageName)) {
        messageNames.add(messageName);
      }
      Long[] existingCounts = messageCounts.get(messageName);
      if (existingCounts == null) {
        messageCounts.put(messageName, new Long[] { Long.valueOf(0), messageCount });
      } else {
        existingCounts[1] = messageCount;
      }
    }
    Collections.sort(messageNames, new Comparator<String>() {
      @Override
      public int compare(String first, String second) {
        return first.compareToIgnoreCase(second);
      }
    });
    for (String messageName: messageNames) {
      Long[] messageCount = messageCounts.get(messageName);
      HTMLNode messageRow = messageCountTable.addChild("tr");
      messageRow.addChild("td", messageName);
      messageRow.addChild("td", "class", "right-align", String.valueOf(messageCount[0]));
      messageRow.addChild("td", "class", "right-align", String.valueOf(messageCount[1]));
    }
  }

  private String idleToString(long now, long idle) {
    if (idle <= 0) {
      return " ";
    }
    long idleMilliseconds = now - idle;
    return TimeUtil.formatTime(idleMilliseconds);
  }
 
  private static String l10n(String string) {
    return NodeL10n.getBase().getString("DarknetConnectionsToadlet."+string);
  }
 
    private static String l10n(String string, String pattern, String value) {
        return NodeL10n.getBase().getString("DarknetConnectionsToadlet."+string, pattern, value);
    }
   
  private String sortString(boolean isReversed, String type) {
    return (isReversed ? ("?sortBy="+type) : ("?sortBy="+type+"&reversed"));
  }
 
  /**
   * Send a simple error page.
   */
  protected void sendErrorPage(ToadletContext ctx, int code, String desc, String message, boolean returnToAddFriends) throws ToadletContextClosedException, IOException {
    PageNode page = ctx.getPageMaker().getPageNode(desc, ctx);
    HTMLNode pageNode = page.outer;
    HTMLNode contentNode = page.content;
   
    HTMLNode infoboxContent = ctx.getPageMaker().getInfobox("infobox-error", desc, contentNode, null, true);
    infoboxContent.addChild("#", message);
    if(returnToAddFriends) {
      infoboxContent.addChild("br");
      infoboxContent.addChild("a", "href", DarknetAddRefToadlet.PATH, l10n("returnToAddAFriendPage"));
      infoboxContent.addChild("br");
    } else {
      infoboxContent.addChild("br");
      infoboxContent.addChild("a", "href", ".", l10n("returnToPrevPage"));
      infoboxContent.addChild("br");
    }
    addHomepageLink(infoboxContent);
   
    writeHTMLReply(ctx, code, desc, pageNode.generate());
  }

}
TOP

Related Classes of freenet.clients.http.ConnectionsToadlet$SimpleColumn

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
ga('send', 'pageview');