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;
    }
  }