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.io;
  
  import org.airsonic.player.util.HttpRange;
  
  import java.io.FilterOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  
  
  /**
   * Special output stream for grabbing only part of a passed stream.
   *
   * @author Sindre Mehus
   */
  public class RangeOutputStream extends FilterOutputStream {
  
      private final HttpRange range;
  
      /**
       * The current position.
       */
      protected long pos;
  
      public RangeOutputStream(OutputStream out, HttpRange range) {
          super(out);
          this.range = range;
      }
  
      /**
       * Wraps the given output stream in a RangeOutputStream, using the values
       * in the given range, unless the range is <code>null</code> in which case
       * the original OutputStream is returned.
       *
       * @param out   The output stream to wrap in a RangeOutputStream.
       * @param range The range, may be <code>null</code>.
       * @return The possibly wrapped output stream.
       */
      public static OutputStream wrap(OutputStream out, HttpRange range) {
          // Skip filtering if there's no range or it represents the entire stream.
          if (range == null || (range.getFirstBytePos() == 0 && !range.isClosed())) {
              return out;
          }
          return new RangeOutputStream(out, range);
      }
  
      /**
       * Writes the byte if it's within the range.
       *
       * @param b The byte to write.
       * @throws IOException Thrown if there was a problem writing to the stream.
       */
      @Override
      public void write(int b) throws IOException {
          if (range.contains(pos)) {
              super.write(b);
          }
          pos++;
      }
  
      /**
       * Writes the subset of the bytes that are within the range.
       *
       * @param b   The bytes to write.
       * @param off The offset to start at.
       * @param len The number of bytes to write.
       * @throws IOException Thrown if there was a problem writing to the stream.
       */
      @Override
      public void write(byte[] b, int off, int len) throws IOException {
          long start = range.getFirstBytePos();
          Long end = range.getLastBytePos();
          if (pos + len >= start && (end == null || pos <= end)) {
              long skipStart = Math.max(0, start - pos);
              long newOff = off + skipStart;
              long newLen = len - skipStart;
              if (end != null) {
                  newLen = min(newLen, end - pos + 1, end - start + 1);
              }
              out.write(b, (int) newOff, (int) newLen);
          }
          pos += len;
      }
  
      private long min(long a, long b, long c) {
          return Math.min(a, Math.min(b, c));
      }
  }
  
  