public void addListener(long min_delta_bytes, final LongTermStatsListener listener) {
    listeners.add(new Object[] {listener, min_delta_bytes, session_total});

    dispatcher.dispatch(
        new AERunnable() {
          public void runSupport() {
            listener.updated(LongTermStatsImpl.this);
          }
        });
  }
  private void write(int record_type, long[] line_stats) {
    synchronized (this) {
      try {
        final long now = SystemTime.getCurrentTime();

        final long now_mins = now / (1000 * 60);

        String[] bits = utc_date_format.format(new Date(now)).split(",");

        String year = bits[0];
        String month = bits[1];
        String day = bits[2];

        String current_rel_file = year + File.separator + month + File.separator + day + ".dat";

        String line;

        String stats_str = "";

        if (record_type == RT_SESSION_START) {

          // absolute values

          for (int i = 0; i < line_stats.length; i++) {

            stats_str += "," + line_stats[i];

            line_stats_prev[i] = 0;
          }

          day_cache = null;

        } else {

          // relative values

          long[] diffs = new long[STAT_ENTRY_COUNT];

          for (int i = 0; i < line_stats.length; i++) {

            long diff = line_stats[i] - line_stats_prev[i];

            session_total += diff;

            diffs[i] = diff;

            stats_str += "," + diff;

            line_stats_prev[i] = line_stats[i];

            stat_averages[i].update(diff);
          }

          if (day_cache != null) {

            if (day_cache.isForDay(year, month, day)) {

              day_cache.addRecord(now_mins, diffs);
            }
          }
        }

        if (record_type != RT_SESSION_STATS) {

          line =
              (record_type == RT_SESSION_START ? "s," : "e,")
                  + VERSION
                  + ","
                  + now_mins
                  + stats_str;

        } else {

          line = stats_str.substring(1);
        }

        if (writer == null || !writer_rel_file.equals(current_rel_file)) {

          // first open of a file or file switch

          if (writer != null) {

            // file switch

            if (record_type != RT_SESSION_START) {

              writer.println(line);
            }

            writer.close();

            if (writer.checkError()) {

              writer = null;

              throw (new IOException("Write faled"));
            }

            writer = null;
          }

          // no point in opening a new file just to record the session-end

          if (record_type != RT_SESSION_END) {

            File file = new File(stats_dir, current_rel_file);

            file.getParentFile().mkdirs();

            writer = new PrintWriter(new FileWriter(file, true));

            writer_rel_file = current_rel_file;

            if (record_type == RT_SESSION_START) {

              writer.println(line);

            } else {

              // first entry in a new file, files always start with a session-start so they
              // can be processed in isolation so reset the session data and start a new one

              st_p_sent += line_stats[0];
              st_d_sent += line_stats[1];
              st_p_received += line_stats[2];
              st_d_received += line_stats[3];
              st_dht_sent += line_stats[4];
              st_dht_received += line_stats[5];

              ss_p_sent += line_stats[0];
              ss_d_sent += line_stats[1];
              ss_p_received += line_stats[2];
              ss_d_received += line_stats[3];
              ss_dht_sent += line_stats[4];
              ss_dht_received += line_stats[5];

              stats_str = "";

              long[] st_stats =
                  new long[] {
                    st_p_sent, st_d_sent, st_p_received, st_d_received, st_dht_sent, st_dht_received
                  };

              for (int i = 0; i < st_stats.length; i++) {

                stats_str += "," + st_stats[i];

                line_stats_prev[i] = 0;
              }

              line = "s," + VERSION + "," + now_mins + stats_str;

              writer.println(line);
            }
          }
        } else {

          writer.println(line);
        }

      } catch (Throwable e) {

        Debug.out("Failed to write long term stats", e);

      } finally {

        if (writer != null) {

          if (record_type == RT_SESSION_END) {

            writer.close();
          }

          if (writer.checkError()) {

            Debug.out("Failed to write long term stats");

            writer.close();

            writer = null;

          } else {

            if (record_type == RT_SESSION_END) {

              writer = null;
            }
          }
        }
      }
    }

    if (record_type != RT_SESSION_END) {

      final List<LongTermStatsListener> to_fire = new ArrayList<LongTermStatsListener>();

      for (Object[] entry : listeners) {

        long diff = session_total - (Long) entry[2];

        if (diff >= (Long) entry[1]) {

          entry[2] = session_total;

          to_fire.add((LongTermStatsListener) entry[0]);
        }
      }

      if (to_fire.size() > 0) {

        dispatcher.dispatch(
            new AERunnable() {
              @Override
              public void runSupport() {
                for (LongTermStatsListener l : to_fire) {

                  try {
                    l.updated(LongTermStatsImpl.this);

                  } catch (Throwable e) {

                    Debug.out(e);
                  }
                }
              }
            });
      }
    }
  }