Persoenliche Mediazentrale
undisclosed
2023-01-22 f9dd4f3a00efe7581e9bd620a9b968f122090f7a
src/de/uhilger/tango/api/StreamHandler.java
@@ -1,22 +1,377 @@
package de.uhilger.tango.api;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import de.uhilger.tango.App;
import de.uhilger.tango.Server;
import static de.uhilger.tango.api.FileHandler.CONTENT_LENGTH;
import static de.uhilger.tango.api.FileHandler.HTTP_GET;
import static de.uhilger.tango.api.FileHandler.RANGE_HEADER;
import static de.uhilger.tango.api.FileHandler.RB_NOT_FOUND;
import static de.uhilger.tango.api.FileHandler.RB_WELCOME_FILE;
import static de.uhilger.tango.api.FileHandler.SC_NOT_FOUND;
import static de.uhilger.tango.api.FileHandler.SC_OK;
import static de.uhilger.tango.api.FileHandler.STR_BLANK;
import static de.uhilger.tango.api.FileHandler.STR_DOT;
import de.uhilger.tango.entity.Ablageort;
import de.uhilger.tango.entity.Abspielliste;
import de.uhilger.tango.entity.Entity;
import de.uhilger.tango.entity.Titel;
import de.uhilger.tango.store.FileStorage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
 * Der StreamHandler liefert ganze Abspiellisten als einzelnen Stream aus.
 * Die in Tango mit dem ListHandler erstellen Abspiellisten werden als ein
 * zusammenhaengender Stream ausgegeben.
 *
 * HTTP GET /tango/api/stream/play/liste/[name]
 *
 * HTTP GET /tango/api/stream/pause/liste/[name]
 * HTTP GET /tango/api/stream/stop/liste/[name]
 * HTTP GET /tango/api/stream/seek/liste/[name]/[sekunden]
 *
 * Die Funktionen des StreamHandlers ergaenzen so die Ausgabe
 * einzelner Media-Dateien als Stream, wie sie mit dem FileHandler und
 * seinen Subklassen sowie mit der MediaSteuerung erfolgen.
 *
 * Der StreamHandler liefert ganze Abspiellisten als einzelnen Stream aus. Die in Tango mit dem
 * ListHandler erstellen Abspiellisten werden als ein zusammenhaengender Stream ausgegeben.
 *
 * Den Inhalt einer Abspielliste als Stream ausgeben HTTP GET /tango/api/stream/liste/[name]
 *
 *
 *
 *
 * die folgenden URLs ggf. wieder wegnehmen
 *
 * HTTP GET /tango/api/stream/play/liste/[name]
 *
 * HTTP GET /tango/api/stream/pause/liste/[name] HTTP GET /tango/api/stream/stop/liste/[name] HTTP
 * GET /tango/api/stream/seek/liste/[name]/[sekunden]
 *
 * Die Funktionen des StreamHandlers ergaenzen so die Ausgabe einzelner Media-Dateien als Stream,
 * wie sie mit dem FileHandler und seinen Subklassen sowie mit der MediaSteuerung erfolgen.
 *
 * @author Ulrich Hilger
 */
public class StreamHandler {
public class StreamHandler extends FileHandler {
  private static final Logger logger = Logger.getLogger(StreamHandler.class.getName());
  public static final String PLAY = "play";
  public static final String LISTE = "liste";
  private HashMap listen;
  private HashMap<String, File> files;
  private HashMap<String, Long> bytes;
  private HashMap<String, String> kataloge;
  private String conf;
  public StreamHandler(String conf) {
    super(""); // wird spaeter gesetzt
    listen = new HashMap<String, Integer>(); // abspiellistenname -> z.Zt. spielender Index
    kataloge = new HashMap(); // url -> abs. pfad
    files = new HashMap<String, File>(); // abspiellistenname -> z zt spielende datei
    bytes = new HashMap<String, Long>(); // abspiellistenname -> gespielte bytes
    this.conf = conf;
    ablageorteLesen();
  }
  @Override
  public void handle(HttpExchange e) throws IOException {
    String path = e.getRequestURI().toString();
    String[] elems = path.split(Server.SLASH);
    String lName = elems[5];
    Integer index;
    File file;
    FileStorage s = new FileStorage(conf);
    Object o = listen.get(lName);
    if (o instanceof Integer) {
      // liste spielt schon
      index = (Integer) o;
      file = files.get(lName);
    } else {
      index = 0;
      // liste spielt noch nicht
      listen.put(lName, index);
      file = getFileToPlay(s, lName, index.intValue());
      files.put(lName, file);
      bytes.put(lName, Long.valueOf(0));
    }
    //String fName = getFileName(e);
    String fName = file.getName();
    if (fName.startsWith(STR_DOT)) {
      sendNotFound(e, fName);
    } else {
      Headers headers = e.getRequestHeaders();
      int indexVal = index.intValue();
      if (headers.containsKey(RANGE_HEADER)) {
        logger.info("range header present, serving list file parts");
        serveListParts(e, s, lName, file, indexVal);
        //serveList(e, s, lName, file, indexVal);
      } else {
        //if (fName.length() < 1 || fName.endsWith(Server.SLASH)) {
        //  ResourceBundle rb = ResourceBundle.getBundle(App.RB_NAME);
        //  fName += getResString(RB_WELCOME_FILE);
        //}
        logger.info("no range header or header ignored, streaming whole files");
        serveList(e, s, lName, file, indexVal);
        //while(file != null) {
        //  files.put(lName, file);
        //  listen.put(lName, index);
        //  serveFile(e, file);
        //  file = getFileToPlay(s, lName, ++indexVal);
        //}
      }
    }
    /*
    String response = lName;
    Headers headers = e.getResponseHeaders();
    headers.add("Content-Type", "application/json");
    e.sendResponseHeaders(200, response.length());
    OutputStream os = e.getResponseBody();
    os.write(response.getBytes());
    os.close();
     */
  }
  
  private void serveListParts(HttpExchange e, FileStorage s, String lName, File file, int indexVal) throws IOException {
    if (file.exists()) {
      setHeaders(e, file);
      //e.getResponseHeaders().set(CONTENT_LENGTH, Long.toString(Long.MAX_VALUE));
      //e.sendResponseHeaders(SC_OK, Long.MAX_VALUE);
      //e.getResponseHeaders().set(CONTENT_LENGTH, Long.toString(Long.MAX_VALUE));
      //e.sendResponseHeaders(SC_OK, Long.MAX_VALUE);
      //e.sendResponseHeaders(SC_PARTIAL_CONTENT, Long.MAX_VALUE);
      logger.info("playing " + file.getName());
      logger.info("file length: " + file.length());
      InputStream is = new FileInputStream(file);
      OutputStream os = e.getResponseBody();
      while (file != null) {
        //if(is instanceof InputStream) {
        //  is.close();
        //}
        //is = new FileInputStream(file);
        files.put(lName, file);
        listen.put(lName, indexVal);
        file = serveFileParts(e, file, is, os, s, lName, indexVal);
        //serveFile(e, file);
        if(bytes.get(lName) == 0l) {
          file = getFileToPlay(s, lName, ++indexVal);
          if(is instanceof InputStream) {
            is.close();
          }
          is = new FileInputStream(file);
          logger.info("file length: " + file.length());
        }
        logger.info("playing " + file.getName());
      }
      //os.flush();
      if(is instanceof InputStream) {
        is.close();
      }
      logger.info("fertig os flush und close ");
      os.flush();
      os.close();
    } else {
      sendNotFound(e, file.getName());
    }
    logger.info("ende");
  }
  protected File serveFileParts(HttpExchange e, File file, InputStream is, OutputStream os, FileStorage s, String lName, int indexVal) throws IOException {
    if (file.exists()) {
      setHeaders(e, file);
      //Long byteCount = bytes.get(lName);
      //logger.info("byteCount at start: " + byteCount);
      long responseLength = 0;
      long start = 0;
      long end;
      String hdr;
      RangeGroup rangeGroup = parseRanges(e, file);
      Iterator<Range> i = rangeGroup.getRanges();
      Headers resHeaders = e.getResponseHeaders();
      while (i.hasNext()) {
        Range range = i.next();
        start = range.getStart();
        //start = 0;
        end = range.getEnd();
        //end = file.length();
        //responseLength += (end - start);
        //start = 0;
        //end = responseLength;
        //range.setStart(start);
        //range.setEnd(end);
        hdr = contentRangeHdr(range, file);
        resHeaders.add(CONTENT_RANGE_HEADER, hdr);
        logger.info("added header " + hdr);
        responseLength += (end - start);
      }
      logger.info("responseLength: " + responseLength);
      e.sendResponseHeaders(SC_PARTIAL_CONTENT, responseLength);
      //e.sendResponseHeaders(SC_PARTIAL_CONTENT, Long.MAX_VALUE);
      //logger.info("responseHeaders gesendet ");
      //if(HTTP_GET.equalsIgnoreCase(e.getRequestMethod())) {
        //InputStream is = new FileInputStream(file);
        //OutputStream os = e.getResponseBody();
        //long streamPos = bytes.get(lName);
        long count = 0;
        if (start > 0) {
          logger.info("skip to " + start);
          is.skip(start);
        }
        int byteRead = is.read();
        logger.info("starte while mit count=" + count);
        while (/*byteRead > -1 && */ count < responseLength) {
          ++count;
          //++streamPos;
          os.write(byteRead);
          byteRead = is.read();
          //logger.info("byteRead " + byteRead);
          if(byteRead < 0 && count < responseLength) {
            logger.info("dateiende, naechste Datei");
            file = getFileToPlay(s, lName, ++indexVal);
            if(file != null) {
              logger.info("playing " + file.getName());
              //streamPos = 0;
              is.close();
              is = new FileInputStream(file);
              byteRead = is.read();
              logger.info("neue Datei, count " + count + " responseLength " + responseLength);
              logger.info("file length: " + file.length());
            } else {
              //logger.info("Liste zuende");
              count = Long.MAX_VALUE;
              logger.info("Liste zuende");
            }
          }
        }
        logger.info("while ende, count " + count);
        //if(streamPos != responseLength) {
        //  bytes.put(lName, streamPos);
        //  logger.info("streamPos " + streamPos);
        //} else {
        //  bytes.put(lName, Long.valueOf(0));
        //  logger.info("streamPos " + 0);
        //}
        //byteCount = count;
        //logger.info("byteCount at end: " + byteCount);
        //os.flush();
        //os.close();
        // is.close();
      }
    //} else {
    //  sendNotFound(e, file.getName());
    //}
    logger.info("ende");
    return file;
  }
  protected String contentRangeHdr(Range range, File file) {
    StringBuilder sb = new StringBuilder();
    sb.append(getResString(RB_BYTES));
    sb.append(STR_BLANK);
    sb.append(range.getStart());
    sb.append(getResString(RB_DASH));
    sb.append(range.getEnd());
    sb.append(Server.SLASH);
    //sb.append(file.length());
    sb.append(Long.MAX_VALUE);
    return sb.toString();
  }
  private void serveList(HttpExchange e, FileStorage s, String lName, File file, int indexVal) throws IOException {
    if (file.exists()) {
      setHeaders(e, file);
      e.getResponseHeaders().set(CONTENT_LENGTH, Long.toString(Long.MAX_VALUE));
      //e.sendResponseHeaders(SC_OK, Long.MAX_VALUE);
      InputStream in = new FileInputStream(file);
      OutputStream out = e.getResponseBody();
      while (file != null) {
        files.put(lName, file);
        listen.put(lName, indexVal);
        serveFile(e, file, in, out);
        file = getFileToPlay(s, lName, ++indexVal);
      }
      out.flush();
      out.close();
      in.close();
    } else {
      sendNotFound(e, file.getName());
    }
  }
  /**
   * Den Inhalt einer Datei ausliefern
   *
   * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum
   * Anfertigen und Senden der Antwort
   * @param file die Datei, deren Inhalt ausgeliefert werden soll
   * @throws IOException falls etwas schief geht entsteht dieser Fehler
   */
  protected void serveFile(HttpExchange e, File file, InputStream in, OutputStream out) throws IOException {
    logger.info("serving file " + file.getName());
      //if(HTTP_GET.equalsIgnoreCase(e.getRequestMethod())) {
        int b = in.read();
        while (b > -1) {
          out.write(b);
          b = in.read();
        }
        logger.info("done serving file " + file.getName());
        //in.close();
        //out.flush();
      //}
  }
  private File getFileToPlay(FileStorage s, String lName, int index) {
    Entity entity = s.read(FileStorage.ST_ABSPIELLISTE, lName);
    if (entity instanceof Abspielliste) {
      Abspielliste liste = (Abspielliste) entity;
      List<Titel> titelListe = liste.getTitel();
      if(titelListe.size() > index) {
        Titel t = titelListe.get(index);
        String katalogUrl = t.getKatalogUrl();
        String pfad = kataloge.get(katalogUrl);
        return new File(pfad + t.getPfad(), t.getName());
      } else {
        return null;
      }
    } else {
      // keine Abspielliste
      return null;
    }
  }
  private void ablageorteLesen() {
    String typ = Ablageort.class.getSimpleName();
    FileStorage store = new FileStorage(conf);
    List<String> orte = store.list(typ);
    Iterator<String> i = orte.iterator();
    while (i.hasNext()) {
      String ortName = i.next();
      Entity e = store.read(typ, ortName);
      if (e instanceof Ablageort) {
        Ablageort ablageort = (Ablageort) e;
        Logger logger = Logger.getLogger(StreamHandler.class.getName());
        //logger.log(Level.FINE, "{0}{1}", new Object[]{ctx, ablageort.getUrl()});
        logger.fine(ablageort.getOrt() + " " + ablageort.getUrl());
        kataloge.put(ablageort.getUrl(), ablageort.getOrt());
        //server.createContext(ctx + ablageort.getUrl(),
        //new ListFileHandler(new File(ablageort.getOrt()).getAbsolutePath(), conf));
      }
    }
  }
}