content: /*
      This file is part of ServerProxy.
      SocketProxy 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.
      Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
      Copyright 2014 (C) Scott Jackson
  */
  
  package net.nullsum.audinaut.util;
  
  import android.util.Log;
  
  import java.io.BufferedOutputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.net.Socket;
  import java.net.SocketException;
  
  public class FileProxy extends ServerProxy {
      private static final String TAG = FileProxy.class.getSimpleName();
  
      protected ProxyTask getTask(Socket client) {
          return new StreamFileTask(client);
      }
  
      protected class StreamFileTask extends ProxyTask {
          File file;
  
          public StreamFileTask(Socket client) {
              super(client);
          }
  
          @Override
          public boolean processRequest() {
              if (!super.processRequest()) {
                  return false;
              }
  
              Log.i(TAG, "Processing request for file " + path);
              file = getFile(path);
              if (!file.exists()) {
                  Log.e(TAG, "File " + path + " does not exist");
                  return false;
              }
  
              // Make sure to not try to read past where the file is downloaded
              return !(cbSkip != 0 && cbSkip >= file.length());
          }
  
          File getFile(String path) {
              return new File(path);
          }
  
          Long getContentLength() {
              return file.length();
          }
  
          long getFileSize() {
              return file.length();
          }
  
          @Override
          public void run() {
              Long contentLength = getContentLength();
  
              // Create HTTP header
              String headers;
              if (cbSkip == 0) {
                  headers = "HTTP/1.0 200 OK\r\n";
              } else {
                  headers = "HTTP/1.0 206 OK\r\n";
                  headers += "Content-Range: bytes " + cbSkip + "-" + (file.length() - 1) + "/";
                  if (contentLength == null) {
                      headers += "*";
                  } else {
                      headers += contentLength;
                  }
                  headers += "\r\n";
  
                  Log.i(TAG, "Streaming starts from: " + cbSkip);
              }
  
              String name = file.getPath();
              int index = name.lastIndexOf('.');
              String ext = "";
              if (index != -1) {
                  ext = name.substring(index + 1).toLowerCase();
              }
              if ("mp3".equals(ext)) {
                  headers += "Content-Type: audio/mpeg\r\n";
              } else {
                  headers += "Content-Type: " + "application/octet-stream" + "\r\n";
              }
  
              long fileSize;
              if (contentLength == null) {
                  fileSize = getFileSize();
              } else {
                  fileSize = contentLength;
                  if (cbSkip > 0) {
                      headers += "Content-Length: " + (fileSize - cbSkip) + "\r\n";
                  } else {
                      headers += "Content-Length: " + fileSize + "\r\n";
                  }
                  headers += "Accept-Ranges: bytes \r\n";
              }
              Log.i(TAG, "Streaming fileSize: " + fileSize);
  
              headers += "Connection: close\r\n";
              headers += "\r\n";
  
              long cbToSend = fileSize - cbSkip;
              OutputStream output = null;
              byte[] buff = new byte[64 * 1024];
              try {
                  output = new BufferedOutputStream(client.getOutputStream(), 32 * 1024);
                  output.write(headers.getBytes());
  
                  // Make sure to have file lock
                  onStart();
  
                  // Loop as long as there's stuff to send
                  while (isRunning && !client.isClosed()) {
                      onResume();
  
                      // See if there's more to send
                      int cbSentThisBatch = 0;
                      if (file.exists()) {
                          FileInputStream input = new FileInputStream(file);
                          input.skip(cbSkip);
                          int cbToSendThisBatch = input.available();
                          while (cbToSendThisBatch > 0) {
                              int cbToRead = Math.min(cbToSendThisBatch, buff.length);
                              int cbRead = input.read(buff, 0, cbToRead);
                              if (cbRead == -1) {
                                  break;
                              }
                              cbToSendThisBatch -= cbRead;
                              cbToSend -= cbRead;
                              output.write(buff, 0, cbRead);
                              output.flush();
                              cbSkip += cbRead;
                              cbSentThisBatch += cbRead;
                          }
                          input.close();
                      }
  
                      // Done regardless of whether or not it thinks it is
                      if (isWorkDone()) {
                          break;
                      }
  
                      // If we did nothing this batch, block for a second
                      if (cbSentThisBatch == 0) {
                          Log.d(TAG, "Blocking until more data appears (" + cbToSend + ")");
                          Thread.sleep(1000);
                      }
                  }
  
                  // Release file lock, use of stream proxy means nothing else is using it
                  onStop();
              } catch (SocketException socketException) {
                  Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly");
  
                  // Release file lock, use of stream proxy means nothing else is using it
                  onStop();
              } catch (Exception e) {
                  Log.e(TAG, "Exception thrown from streaming task:");
                  Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
              }
  
              // Cleanup
              try {
                  if (output != null) {
                      output.close();
                  }
                  client.close();
              } catch (IOException e) {
                  Log.e(TAG, "IOException while cleaning up streaming task:");
                  Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
              }
          }
  
          public void onStart() {
  
          }
  
          public void onStop() {
  
          }
  
          public void onResume() {
  
          }
  
          public boolean isWorkDone() {
              return cbSkip >= file.length();
          }
      }
  }
  