content: /*
   This file is part of Airsonic.
  
   Airsonic is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
  
   Airsonic is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
  
   You should have received a copy of the GNU General Public License
   along with Airsonic.  If not, see <http://www.gnu.org/licenses/>.
  
   Copyright 2016 (C) Airsonic Authors
   Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
   */
  package org.airsonic.player.domain;
  
  import org.apache.commons.collections4.queue.CircularFifoQueue;
  
  import java.io.File;
  
  /**
   * Status for a single transfer (stream, download or upload).
   *
   * @author Sindre Mehus
   */
  public class TransferStatus {
  
      private static final int HISTORY_LENGTH = 200;
      private static final long SAMPLE_INTERVAL_MILLIS = 5000;
  
      private Player player;
      private File file;
      private long bytesTransfered;
      private long bytesSkipped;
      private long bytesTotal;
      private final SampleHistory history = new SampleHistory();
      private boolean terminated;
      private boolean active = true;
  
      /**
       * Return the number of bytes transferred.
       *
       * @return The number of bytes transferred.
       */
      public synchronized long getBytesTransfered() {
          return bytesTransfered;
      }
  
      /**
       * Adds the given byte count to the total number of bytes transferred.
       *
       * @param byteCount The byte count.
       */
      public synchronized void addBytesTransfered(long byteCount) {
          setBytesTransfered(bytesTransfered + byteCount);
      }
  
      /**
       * Sets the number of bytes transferred.
       *
       * @param bytesTransfered The number of bytes transferred.
       */
      public synchronized void setBytesTransfered(long bytesTransfered) {
          this.bytesTransfered = bytesTransfered;
          createSample(bytesTransfered, false);
      }
  
      private void createSample(long bytesTransfered, boolean force) {
          long now = System.currentTimeMillis();
  
          if (history.isEmpty()) {
              history.add(new Sample(bytesTransfered, now));
          } else {
              Sample lastSample = history.getLast();
              if (force || now - lastSample.getTimestamp() > TransferStatus.SAMPLE_INTERVAL_MILLIS) {
                  history.add(new Sample(bytesTransfered, now));
              }
          }
      }
  
      /**
       * Returns the number of milliseconds since the transfer status was last updated.
       *
       * @return Number of milliseconds, or <code>0</code> if never updated.
       */
      public synchronized long getMillisSinceLastUpdate() {
          if (history.isEmpty()) {
              return 0L;
          }
          return System.currentTimeMillis() - history.getLast().timestamp;
      }
  
      /**
       * Returns the total number of bytes, or 0 if unknown.
       *
       * @return The total number of bytes, or 0 if unknown.
       */
      public long getBytesTotal() {
          return bytesTotal;
      }
  
      /**
       * Sets the total number of bytes, or 0 if unknown.
       *
       * @param bytesTotal The total number of bytes, or 0 if unknown.
       */
      public void setBytesTotal(long bytesTotal) {
          this.bytesTotal = bytesTotal;
      }
  
      /**
       * Returns the number of bytes that has been skipped (for instance when
       * resuming downloads).
       *
       * @return The number of skipped bytes.
       */
      public synchronized long getBytesSkipped() {
          return bytesSkipped;
      }
  
      /**
       * Sets the number of bytes that has been skipped (for instance when
       * resuming downloads).
       *
       * @param bytesSkipped The number of skipped bytes.
       */
      public synchronized void setBytesSkipped(long bytesSkipped) {
          this.bytesSkipped = bytesSkipped;
      }
  
  
      /**
       * Adds the given byte count to the total number of bytes skipped.
       *
       * @param byteCount The byte count.
       */
      public synchronized void addBytesSkipped(long byteCount) {
          bytesSkipped += byteCount;
      }
  
      /**
       * Returns the file that is currently being transferred.
       *
       * @return The file that is currently being transferred.
       */
      public synchronized File getFile() {
          return file;
      }
  
      /**
       * Sets the file that is currently being transferred.
       *
       * @param file The file that is currently being transferred.
       */
      public synchronized void setFile(File file) {
          this.file = file;
      }
  
      /**
       * Returns the remote player for the stream.
       *
       * @return The remote player for the stream.
       */
      public synchronized Player getPlayer() {
          return player;
      }
  
      /**
       * Sets the remote player for the stream.
       *
       * @param player The remote player for the stream.
       */
      public synchronized void setPlayer(Player player) {
          this.player = player;
      }
  
      /**
       * Returns a history of samples for the stream
       *
       * @return A (copy of) the history list of samples.
       */
      public synchronized SampleHistory getHistory() {
          return new SampleHistory(history);
      }
  
      /**
       * Returns the history length in milliseconds.
       *
       * @return The history length in milliseconds.
       */
      public long getHistoryLengthMillis() {
          return TransferStatus.SAMPLE_INTERVAL_MILLIS * (TransferStatus.HISTORY_LENGTH - 1);
      }
  
      /**
       * Indicate that the stream should be terminated.
       */
      public void terminate() {
          terminated = true;
      }
  
      /**
       * Returns whether this stream has been terminated.
       * Not that the <em>terminated status</em> is cleared by this method.
       *
       * @return Whether this stream has been terminated.
       */
      public boolean terminated() {
          boolean result = terminated;
          terminated = false;
          return result;
      }
  
      /**
       * Returns whether this transfer is active, i.e., if the connection is still established.
       *
       * @return Whether this transfer is active.
       */
      public boolean isActive() {
          return active;
      }
  
      /**
       * Sets whether this transfer is active, i.e., if the connection is still established.
       *
       * @param active Whether this transfer is active.
       */
      public synchronized void setActive(boolean active) {
          this.active = active;
  
          if (active) {
              bytesSkipped = 0L;
              bytesTotal = 0L;
              setBytesTransfered(0L);
          } else {
              createSample(getBytesTransfered(), true);
          }
      }
  
      /**
       * A sample containing a timestamp and the number of bytes transferred up to that point in time.
       */
      public static class Sample {
          private long bytesTransfered;
          private long timestamp;
  
          /**
           * Creates a new sample.
           *
           * @param bytesTransfered The total number of bytes transferred.
           * @param timestamp       A point in time, in milliseconds.
           */
          public Sample(long bytesTransfered, long timestamp) {
              this.bytesTransfered = bytesTransfered;
              this.timestamp = timestamp;
          }
  
          /**
           * Returns the number of bytes transferred.
           *
           * @return The number of bytes transferred.
           */
          public long getBytesTransfered() {
              return bytesTransfered;
          }
  
          /**
           * Returns the timestamp of the sample.
           *
           * @return The timestamp in milliseconds.
           */
          public long getTimestamp() {
              return timestamp;
          }
      }
  
      @Override
      public String toString() {
          return "TransferStatus-" + hashCode() + " [player: " + player.getId() + ", file: " +
                  file + ", terminated: " + terminated + ", active: " + active + "]";
      }
  
      /**
       * Contains recent history of samples.
       */
      public static class SampleHistory extends CircularFifoQueue<Sample> {
  
          public SampleHistory() {
              super(HISTORY_LENGTH);
          }
  
          public SampleHistory(SampleHistory other) {
              super(HISTORY_LENGTH);
              addAll(other);
          }
  
          public Sample getLast() {
              return this.get(this.size() - 1);
          }
      }
  }
  