/** write out the data from the profile to the stream */
  public void writeProfile(PeerProfile profile) {
    if (isExpired(profile.getLastSendSuccessful())) return;

    File f = pickFile(profile);
    long before = _context.clock().now();
    OutputStream fos = null;
    try {
      fos = new BufferedOutputStream(new GZIPOutputStream(new SecureFileOutputStream(f)));
      writeProfile(profile, fos);
    } catch (IOException ioe) {
      _log.error("Error writing profile to " + f);
    } finally {
      if (fos != null)
        try {
          fos.close();
        } catch (IOException ioe) {
        }
    }
    long delay = _context.clock().now() - before;
    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";
    } else if (!_context.profileOrganizer().isHighCapacity(profile.getPeer())) {
      groups = "Standard";
    } else {
      if (_context.profileOrganizer().isFast(profile.getPeer())) groups = "Fast, High Capacity";
      else groups = "High Capacity";

      if (_context.profileOrganizer().isWellIntegrated(profile.getPeer()))
        groups = groups + ", Integrated";
    }

    StringBuilder buf = new StringBuilder(512);
    buf.append("########################################################################")
        .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("# 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);
    add(buf, "speedBonus", profile.getSpeedBonus(), "Manual adjustment to the speed score");
    add(
        buf,
        "capacityBonus",
        profile.getCapacityBonus(),
        "Manual adjustment to the capacity score");
    add(
        buf,
        "integrationBonus",
        profile.getIntegrationBonus(),
        "Manual adjustment to the integration score");
    addDate(
        buf,
        "firstHeardAbout",
        profile.getFirstHeardAbout(),
        "When did we first get a reference to this peer?");
    addDate(
        buf,
        "lastHeardAbout",
        profile.getLastHeardAbout(),
        "When did we last get a reference to this peer?");
    addDate(
        buf,
        "lastHeardFrom",
        profile.getLastHeardFrom(),
        "When did we last get a message from the 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?");
    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());

    if (profile.getIsExpanded()) {
      // only write out expanded data if, uh, we've got it
      profile.getTunnelHistory().store(out);
      // profile.getReceiveSize().store(out, "receiveSize");
      // profile.getSendSuccessSize().store(out, "sendSuccessSize");
      profile.getTunnelCreateResponseTime().store(out, "tunnelCreateResponseTime");
      profile.getTunnelTestResponseTime().store(out, "tunnelTestResponseTime");
    }

    if (profile.getIsExpandedDB()) {
      profile.getDBHistory().store(out);
      profile.getDbIntroduction().store(out, "dbIntroduction");
      profile.getDbResponseTime().store(out, "dbResponseTime");
    }
  }
 private File pickFile(PeerProfile profile) {
   String hash = profile.getPeer().toBase64();
   File dir = new File(_profileDir, DIR_PREFIX + hash.charAt(0));
   return new File(dir, PREFIX + hash + SUFFIX);
 }
  public PeerProfile readProfile(File file) {
    Hash peer = getHash(file.getName());
    try {
      if (peer == null) {
        _log.error("The file " + file.getName() + " is not a valid hash");
        return null;
      }
      PeerProfile profile = new PeerProfile(_context, peer);
      Properties props = new Properties();

      loadProps(props, file);

      long lastSentToSuccessfully = getLong(props, "lastSentToSuccessfully");
      if (isExpired(lastSentToSuccessfully)) {
        if (_log.shouldLog(Log.INFO))
          _log.info(
              "Dropping old profile "
                  + file.getName()
                  + ", 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"));
      profile.setIntegrationBonus(getLong(props, "integrationBonus"));
      profile.setSpeedBonus(getLong(props, "speedBonus"));

      profile.setLastHeardAbout(getLong(props, "lastHeardAbout"));
      profile.setFirstHeardAbout(getLong(props, "firstHeardAbout"));
      profile.setLastSendSuccessful(getLong(props, "lastSentToSuccessfully"));
      profile.setLastSendFailed(getLong(props, "lastFailedSend"));
      profile.setLastHeardFrom(getLong(props, "lastHeardFrom"));
      profile.setTunnelTestTimeAverage(getDouble(props, "tunnelTestTimeAverage"));
      profile.setPeakThroughputKBps(getDouble(props, "tunnelPeakThroughput"));
      profile.setPeakTunnelThroughputKBps(getDouble(props, "tunnelPeakTunnelThroughput"));
      profile.setPeakTunnel1mThroughputKBps(getDouble(props, "tunnelPeakTunnel1mThroughput"));

      profile.getTunnelHistory().load(props);

      // In the interest of keeping the in-memory profiles small,
      // don't load the DB info at all unless there is something interesting there
      // (i.e. floodfills)
      // It seems like we do one or two lookups as a part of handshaking?
      // Not sure, to be researched.
      if (getLong(props, "dbHistory.successfulLookups") > 1
          || getLong(props, "dbHistory.failedlLokups") > 1) {
        profile.expandDBProfile();
        profile.getDBHistory().load(props);
        profile.getDbIntroduction().load(props, "dbIntroduction", true);
        profile.getDbResponseTime().load(props, "dbResponseTime", true);
      }

      // profile.getReceiveSize().load(props, "receiveSize", true);
      // profile.getSendSuccessSize().load(props, "sendSuccessSize", true);
      profile.getTunnelCreateResponseTime().load(props, "tunnelCreateResponseTime", true);
      profile.getTunnelTestResponseTime().load(props, "tunnelTestResponseTime", true);

      if (_log.shouldLog(Log.DEBUG))
        _log.debug("Loaded the profile for " + peer.toBase64() + " from " + file.getName());

      return profile;
    } catch (Exception e) {
      if (_log.shouldLog(Log.WARN))
        _log.warn("Error loading properties from " + file.getAbsolutePath(), e);
      file.delete();
      return null;
    }
  }