From 5bf530d39a7e06bdd982a5c2495ff9a04fd83da4 Mon Sep 17 00:00:00 2001
From: undisclosed
Date: Wed, 11 Jan 2023 17:38:22 +0000
Subject: [PATCH] Erste Versuche mit StreamHandler

---
 src/de/uhilger/tango/api/StreamHandler.java |  385 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 src/tango_de_DE.properties                  |    1 
 src/de/uhilger/tango/api/FileHandler.java   |    3 
 src/de/uhilger/tango/Server.java            |    5 
 4 files changed, 377 insertions(+), 17 deletions(-)

diff --git a/src/de/uhilger/tango/Server.java b/src/de/uhilger/tango/Server.java
index 88be4db..d5d445a 100644
--- a/src/de/uhilger/tango/Server.java
+++ b/src/de/uhilger/tango/Server.java
@@ -25,6 +25,7 @@
 import de.uhilger.tango.api.MediaSteuerung;
 import de.uhilger.tango.api.StopServerHandler;
 import de.uhilger.tango.api.StorageHandler;
+import de.uhilger.tango.api.StreamHandler;
 import de.uhilger.tango.store.FileStorage;
 import de.uhilger.tango.entity.Ablageort;
 import java.io.File;
@@ -54,7 +55,8 @@
   public static final String RB_STORE = "store";
   public static final String RB_STRG = "strg";
   public static final String RB_GSTRG = "gstrg";
-  public static final String RB_ALIST= "alist";
+  public static final String RB_ALIST = "alist";
+  public static final String RB_STRM = "strm";
   //public static final String RB_UI_ROOT = "uiroot";
   public static final String RB_STOP_SERVER = "stopServer";
   //public static final String RB_ABLAGE_TEST = "testAblage";
@@ -121,6 +123,7 @@
     server.createContext(ctx + rb.getString(RB_STRG), new MediaSteuerung(conf));
     server.createContext(ctx + rb.getString(RB_GSTRG), new GeraetSteuerung(conf));
     server.createContext(ctx + rb.getString(RB_ALIST), new ListHandler(conf));
+    server.createContext(ctx + rb.getString(RB_STRM), new StreamHandler(conf));
     server.createContext(ctx + rb.getString(RB_STOP_SERVER), new StopServerHandler());
     //server.setExecutor(Executors.newFixedThreadPool(20));
     server.setExecutor(Executors.newFixedThreadPool(5));
diff --git a/src/de/uhilger/tango/api/FileHandler.java b/src/de/uhilger/tango/api/FileHandler.java
index 40edfed..d0299e1 100644
--- a/src/de/uhilger/tango/api/FileHandler.java
+++ b/src/de/uhilger/tango/api/FileHandler.java
@@ -288,7 +288,8 @@
       if (values.length < 2) {
         // Fall 3
         range.setStart(Long.parseLong(values[0]));
-        range.setEnd(file.length());
+        //range.setEnd(file.length());
+        range.setEnd(Long.MAX_VALUE);
       } else {
         if (values[0].length() < 1) {
           // Fall 1
diff --git a/src/de/uhilger/tango/api/StreamHandler.java b/src/de/uhilger/tango/api/StreamHandler.java
index d19a82c..24bc407 100644
--- a/src/de/uhilger/tango/api/StreamHandler.java
+++ b/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));
+      }
+    }
+  }
+
 }
diff --git a/src/tango_de_DE.properties b/src/tango_de_DE.properties
index 27894da..b493ea4 100644
--- a/src/tango_de_DE.properties
+++ b/src/tango_de_DE.properties
@@ -20,6 +20,7 @@
 alist=/api/alist
 strg=/api/strg
 gstrg=/api/gstrg
+strm=/api/stream
 epliste=liste
 eplisteAlles=listealles
 stopServer=/api/server/stop

--
Gitblit v1.9.3