Calypso neu gebaut auf Nutzung von mpv, alte Fassung entfernt
 
	
	
	
	
	
	
		
		2 files modified
	
		
		7 files added
	
		
		2 files renamed
	
		
		29 files deleted
	
	
 
	
	
	
	
	
	
	
	
 |  |  | 
 |  |  | /* | 
 |  |  |     Calypso - Media Player Remote Control via HTTP for Raspberry Pi | 
 |  |  |     Copyright (C) 2021-2023  Ulrich Hilger | 
 |  |  |  | 
 |  |  |     This program is free software: you can redistribute it and/or modify | 
 |  |  |     it under the terms of the GNU Affero General Public License as | 
 |  |  |     published by the Free Software Foundation, either version 3 of the | 
 |  |  |     License, or (at your option) any later version. | 
 |  |  |  | 
 |  |  |     This program 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 Affero General Public License for more details. | 
 |  |  |  | 
 |  |  |     You should have received a copy of the GNU Affero General Public License | 
 |  |  |     along with this program.  If not, see <https://www.gnu.org/licenses/>. | 
 |  |  | */ | 
 |  |  |  | 
 |  |  | package de.uhilger.calypso.neu; | 
 |  |  |  | 
 |  |  | import de.uhilger.calypso.neu.http.Server; | 
 |  |  | import java.util.HashMap; | 
 |  |  |  | 
 |  |  | /** | 
 
 |  |  | 
 |  |  |     Logger.getLogger(AppProperties.class.getName()).log(Level.INFO, fName); | 
 |  |  |     InputStream in = null; | 
 |  |  |     try { | 
 |  |  |       // Einstellungen aus Datei lesen | 
 |  |  |       //AppProperties einst = new AppProperties(); | 
 |  |  |       File einstellungenDatei = new File(fName); | 
 |  |  |       in = new FileInputStream(einstellungenDatei); | 
 |  |  |       | 
 |  |  |       //BufferedReader br = new BufferedReader(new InputStreamReader(in)); | 
 |  |  |       //String line = br.readLine(); | 
 |  |  |       //while(line != null) { | 
 |  |  |       //  System.out.println(line); | 
 |  |  |       //  line = br.readLine(); | 
 |  |  |       //} | 
 |  |  |       //br.close(); | 
 |  |  |        | 
 |  |  |        | 
 |  |  |       load(in); | 
 |  |  |       in.close(); | 
 |  |  |     } catch (FileNotFoundException ex) { | 
 |  |  | 
 |  |  |         Logger.getLogger(AppProperties.class.getName()).log(Level.SEVERE, null, ex); | 
 |  |  |       } | 
 |  |  |     } | 
 |  |  |        | 
 |  |  |      | 
 |  |  |   } | 
 |  |  |    | 
 |  |  | } | 
 
| File was renamed from src/de/uhilger/calypso/MeldeThread.java | 
 |  |  | 
 |  |  |     along with this program.  If not, see <https://www.gnu.org/licenses/>. | 
 |  |  | */ | 
 |  |  |  | 
 |  |  | package de.uhilger.calypso; | 
 |  |  | package de.uhilger.calypso.neu; | 
 |  |  |  | 
 |  |  | import java.util.logging.Logger; | 
 |  |  | import java.util.logging.Level; | 
 |  |  | 
 |  |  |     this.meldeUrlStr = meldeUrlStr; | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |   /** | 
 |  |  |    * ausfuehren des Threads | 
 |  |  |    */ | 
 |  |  |   @Override | 
 |  |  |   public void run() { | 
 |  |  |     try { | 
 |  |  |       exitValue = omxplayer.waitFor(); | 
 |  |  |       prozessBeendetMelden(); | 
 |  |  |     } catch(Exception ex) { | 
 |  |  |       lauscher.clear(); | 
 |  |  |       lauscher = null; | 
 |  |  |     } catch(InterruptedException ex) { | 
 |  |  |       logger.log(Level.FINE, ex.getMessage(), ex); | 
 |  |  |     } finally { | 
 |  |  |       aufraeumen(); | 
 
| File was renamed from src/de/uhilger/calypso/ProzessLauscher.java | 
 |  |  | 
 |  |  |     along with this program.  If not, see <https://www.gnu.org/licenses/>. | 
 |  |  | */ | 
 |  |  |  | 
 |  |  | package de.uhilger.calypso; | 
 |  |  |  | 
 |  |  | package de.uhilger.calypso.neu; | 
 |  |  |  | 
 |  |  | public interface ProzessLauscher { | 
 |  |  |    | 
 
| New file | 
 |  |  | 
 |  |  | package de.uhilger.calypso.neu; | 
 |  |  |  | 
 |  |  | import java.io.IOException; | 
 |  |  | import java.net.HttpURLConnection; | 
 |  |  | import java.net.URL; | 
 |  |  | import java.util.logging.Level; | 
 |  |  | import java.util.logging.Logger; | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * | 
 |  |  |  * @author Ulrich Hilger | 
 |  |  |  */ | 
 |  |  | public class Rueckmelder implements ProzessLauscher { | 
 |  |  |  | 
 |  |  |  | 
 |  |  |  | 
 |  |  |   @Override | 
 |  |  |   public void prozessBeendet(String meldeUrlStr) { | 
 |  |  |     Logger logger = Logger.getLogger(de.uhilger.calypso.neu.http.ApiHandler.class.getName()); | 
 |  |  |     logger.log(Level.INFO, | 
 |  |  |             "Abspielen beendet, sende Meldung an {0}.", | 
 |  |  |             new Object[]{meldeUrlStr}); | 
 |  |  |     try { | 
 |  |  |       HttpURLConnection conn = (HttpURLConnection) new URL(meldeUrlStr).openConnection(); | 
 |  |  |       conn.setRequestMethod("GET"); | 
 |  |  |       conn.connect(); | 
 |  |  |       int status = conn.getResponseCode(); | 
 |  |  |       logger.log(Level.INFO, | 
 |  |  |               "Abspielen beendet, Meldung an {0} mit Statuscode {1} gesendet.", | 
 |  |  |               new Object[]{meldeUrlStr, status}); | 
 |  |  |       /* | 
 |  |  |             fuer den Fall, dass ein Stopp-Signal den Player nicht erreicht  | 
 |  |  |             oder dort nicht funktioniert, gibt es keine Moeglichkeit festzustellen, | 
 |  |  |             dass der Player noch spielt. Damit in einem solchen Fall der Zeiger  | 
 |  |  |             auf den Abspielprozess nicht verloren geht, wird  der Zeiger nicht  | 
 |  |  |             auf null gesetzt. | 
 |  |  |        */ | 
 |  |  |       //App.setPlayerProcess(null); | 
 |  |  |     } catch (IOException ex) { | 
 |  |  |       logger.log(Level.INFO, ex.getMessage(), ex); | 
 |  |  |     } | 
 |  |  |   } | 
 |  |  |  | 
 |  |  |  | 
 |  |  |    | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |     Calypso - Media Player Remote Control via HTTP for Raspberry Pi | 
 |  |  |     Copyright (C) 2021-2023  Ulrich Hilger | 
 |  |  |  | 
 |  |  |     This program is free software: you can redistribute it and/or modify | 
 |  |  |     it under the terms of the GNU Affero General Public License as | 
 |  |  |     published by the Free Software Foundation, either version 3 of the | 
 |  |  |     License, or (at your option) any later version. | 
 |  |  |  | 
 |  |  |     This program 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 Affero General Public License for more details. | 
 |  |  |  | 
 |  |  |     You should have received a copy of the GNU Affero General Public License | 
 |  |  |     along with this program.  If not, see <https://www.gnu.org/licenses/>. | 
 |  |  | */ | 
 |  |  |  | 
 |  |  | package de.uhilger.calypso.neu.actor; | 
 |  |  |  | 
 |  |  | import de.uhilger.calypso.neu.MeldeThread; | 
 |  |  | import de.uhilger.calypso.neu.Rueckmelder; | 
 |  |  | import de.uhilger.calypso.neu.http.Server; | 
 |  |  | import java.io.IOException; | 
 |  |  | import java.util.Map; | 
 |  |  | import java.util.logging.Level; | 
 |  |  | import java.util.logging.Logger; | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * Der PlayActor loest das Abspielen eines Titels aus.  | 
 |  |  |  *  | 
 |  |  |  * Das koennte auch als Shell-Skript geschehen, aber abhaengig  | 
 |  |  |  * vom verwendeten Abspielprogramm benoetigt man das Prozessobjekt  | 
 |  |  |  * des laufenden Abspielprozesses im Verlauf des Abspielens noch, um  | 
 |  |  |  * weitere Kommandos zu senden. | 
 |  |  |  *  | 
 |  |  |  * Beim Abspieler mpv werden Kommandos allerdings ueber Unix Domain  | 
 |  |  |  * Sockets gesendet, hierfuer waere also das Objekt des laufenden  | 
 |  |  |  * Abspielprozesses nicht noetig. Man benoetigt es aber auch um  | 
 |  |  |  * festzustellen, ob der Abspielprozess beendet ist. | 
 |  |  |  *  | 
 |  |  |  * @author Ulrich Hilger | 
 |  |  |  */ | 
 |  |  | public class PlayActor { | 
 |  |  |  | 
 |  |  |   public Process run(Map parameter) { | 
 |  |  |     String meldeUrlStr = null; | 
 |  |  |     Object o = parameter.get("r"); | 
 |  |  |     if (o instanceof String) { | 
 |  |  |       meldeUrlStr = (String) o; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     StringBuilder kommando = new StringBuilder(); | 
 |  |  |     o = parameter.get("titel"); | 
 |  |  |     if (o instanceof String) { | 
 |  |  |       String titel = (String) o; | 
 |  |  |       if (titel.toLowerCase().endsWith(".mp3")) { | 
 |  |  |         kommando.append("mpv --input-ipc-server=/tmp/mpvsocket --no-terminal --vo=null "); | 
 |  |  |       } else { | 
 |  |  |         kommando.append("mpv --input-ipc-server=/tmp/mpvsocket --no-terminal "); | 
 |  |  |       } | 
 |  |  |       kommando.append(Server.BLANK); | 
 |  |  |       kommando.append(titel); | 
 |  |  |       Logger.getLogger(PlayActor.class.getName()).log(Level.FINE, kommando.toString()); | 
 |  |  |       Process player_process; | 
 |  |  |       try { | 
 |  |  |         player_process = Runtime.getRuntime().exec(kommando.toString()); | 
 |  |  |         if (meldeUrlStr != null) { | 
 |  |  |           MeldeThread mt = new MeldeThread(); | 
 |  |  |           mt.setProcess(player_process); | 
 |  |  |           mt.lauscherHinzufuegen(new Rueckmelder()); | 
 |  |  |           mt.setMeldeUrl(meldeUrlStr); | 
 |  |  |           mt.start(); | 
 |  |  |         } | 
 |  |  |         return player_process; | 
 |  |  |       } catch (IOException ex) { | 
 |  |  |         Logger logger = Logger.getLogger(PlayActor.class.getName()); | 
 |  |  |         logger.log(Level.SEVERE, ex.getLocalizedMessage(), ex); | 
 |  |  |         ex.printStackTrace(); | 
 |  |  |         return null; | 
 |  |  |       } | 
 |  |  |     } else { | 
 |  |  |       Logger.getLogger(PlayActor.class.getName()).log(Level.INFO, "Titel fehlt"); | 
 |  |  |       return null; | 
 |  |  |     } | 
 |  |  |   } | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |     Calypso - Media Player Remote Control via HTTP for Raspberry Pi | 
 |  |  |     Copyright (C) 2021-2023  Ulrich Hilger | 
 |  |  |  | 
 |  |  |     This program is free software: you can redistribute it and/or modify | 
 |  |  |     it under the terms of the GNU Affero General Public License as | 
 |  |  |     published by the Free Software Foundation, either version 3 of the | 
 |  |  |     License, or (at your option) any later version. | 
 |  |  |  | 
 |  |  |     This program 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 Affero General Public License for more details. | 
 |  |  |  | 
 |  |  |     You should have received a copy of the GNU Affero General Public License | 
 |  |  |     along with this program.  If not, see <https://www.gnu.org/licenses/>. | 
 |  |  | */ | 
 |  |  |  | 
 |  |  | package de.uhilger.calypso.neu.actor; | 
 |  |  |  | 
 |  |  | import de.uhilger.calypso.neu.http.Server; | 
 |  |  | import java.io.File; | 
 |  |  | import java.io.IOException; | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * | 
 |  |  |  * Der mpv Abspieler erhaelt Kommandos waehrend des Abspielens  | 
 |  |  |  * ueber Unix Domain Sockets. Da diese erst mit Java 16 untersuetzt  | 
 |  |  |  * werden und Java 16 fuer den Raspberry Pi noch nicht erhaeltlich  | 
 |  |  |  * ist, werden diese Kommandos als Shell-Skript unter Verwendung  | 
 |  |  |  * des Linux-Programms socat gesendet. | 
 |  |  |  *  | 
 |  |  |  * Der Shell Actor fuehrt Skripte aus. Wenn ein Kommando Parameter  | 
 |  |  |  * erfordert, werden diese aus dem Query-Teil des HTTP-Aufrufes  | 
 |  |  |  * entnommen. | 
 |  |  |  *  | 
 |  |  |  *  | 
 |  |  |  * @author Ulrich Hilger | 
 |  |  |  */ | 
 |  |  | public class ShellActor { | 
 |  |  |    | 
 |  |  |   /** | 
 |  |  |    * Ein Skript auf der Kommandozeile ausfuehren | 
 |  |  |    *  | 
 |  |  |    * @param skriptDir das Verzeichnis, in dem das Skript liegt | 
 |  |  |    * @param kommando der Name des Skript | 
 |  |  |    * @throws IOException  | 
 |  |  |    */ | 
 |  |  |   //public void run(String skriptDir, String kommando) throws IOException { | 
 |  |  |   //  File skriptFile = new File(skriptDir, kommando); | 
 |  |  |   //  Process p = Runtime.getRuntime().exec(skriptFile.getAbsolutePath()); | 
 |  |  |   //} | 
 |  |  |    | 
 |  |  |   /** | 
 |  |  |    *  | 
 |  |  |    * @param skriptDir das Verzeichnis, in dem das Skript liegt | 
 |  |  |    * @param kommando der Name des Skript | 
 |  |  |    * @param params die Parameter des Shell-Kommandos, null, wenn keine | 
 |  |  |    * @throws IOException  | 
 |  |  |    */ | 
 |  |  |   public void run(String skriptDir, String kommando, String... params) throws IOException { | 
 |  |  |     StringBuilder sb = new StringBuilder(); | 
 |  |  |     File skriptFile = new File(skriptDir, kommando); | 
 |  |  |     sb.append(skriptFile.getAbsolutePath()); | 
 |  |  |     if(params instanceof String[]) { | 
 |  |  |       for (String param : params) { | 
 |  |  |         sb.append(Server.BLANK); | 
 |  |  |         sb.append(param); | 
 |  |  |       } | 
 |  |  |     } | 
 |  |  |     Process p = Runtime.getRuntime().exec(sb.toString());     | 
 |  |  |   } | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |     Calypso - Media Player Remote Control via HTTP for Raspberry Pi | 
 |  |  |     Copyright (C) 2021-2023  Ulrich Hilger | 
 |  |  |  | 
 |  |  |     This program is free software: you can redistribute it and/or modify | 
 |  |  |     it under the terms of the GNU Affero General Public License as | 
 |  |  |     published by the Free Software Foundation, either version 3 of the | 
 |  |  |     License, or (at your option) any later version. | 
 |  |  |  | 
 |  |  |     This program 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 Affero General Public License for more details. | 
 |  |  |  | 
 |  |  |     You should have received a copy of the GNU Affero General Public License | 
 |  |  |     along with this program.  If not, see <https://www.gnu.org/licenses/>. | 
 |  |  | */ | 
 |  |  |  | 
 |  |  | package de.uhilger.calypso.neu.actor; | 
 |  |  |  | 
 |  |  | import com.sun.net.httpserver.HttpContext; | 
 |  |  | import java.util.Timer; | 
 |  |  | import java.util.TimerTask; | 
 |  |  | import java.util.logging.Logger; | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * Ein HTTP-Handler zum Stoppen der Anwendung | 
 |  |  |  * | 
 |  |  |  * @author Ulrich Hilger | 
 |  |  |  */ | 
 |  |  | public class StopServerActor { | 
 |  |  |    | 
 |  |  |   public void run(HttpContext thisContext) { | 
 |  |  |     Logger.getLogger(StopServerActor.class.getName()).info("Server wird gestoppt.."); | 
 |  |  |     thisContext.getServer().stop(1); // diesen Server stoppen | 
 |  |  |     Timer timer = new Timer(); | 
 |  |  |     timer.schedule(new AppStopper(), 1500); // die App auch beenden | 
 |  |  |   } | 
 |  |  |  | 
 |  |  |   /** | 
 |  |  |    * Die Klasse AppStopper ermöglicht das asnychrone bzw.  | 
 |  |  |    * zeitgesteuerte Stoppen der Anwendung. | 
 |  |  |    */ | 
 |  |  |   class AppStopper extends TimerTask { | 
 |  |  |  | 
 |  |  |     @Override | 
 |  |  |     public void run() { | 
 |  |  |       Logger.getLogger(StopServerActor.class.getName()).info("Calypso beendet."); | 
 |  |  |       System.exit(0); | 
 |  |  |     } | 
 |  |  |   } | 
 |  |  |  | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |     Calypso - Media Player Remote Control via HTTP for Raspberry Pi | 
 |  |  |     Copyright (C) 2021-2023  Ulrich Hilger | 
 |  |  |  | 
 |  |  |     This program is free software: you can redistribute it and/or modify | 
 |  |  |     it under the terms of the GNU Affero General Public License as | 
 |  |  |     published by the Free Software Foundation, either version 3 of the | 
 |  |  |     License, or (at your option) any later version. | 
 |  |  |  | 
 |  |  |     This program 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 Affero General Public License for more details. | 
 |  |  |  | 
 |  |  |     You should have received a copy of the GNU Affero General Public License | 
 |  |  |     along with this program.  If not, see <https://www.gnu.org/licenses/>. | 
 |  |  | */ | 
 |  |  |  | 
 |  |  | package de.uhilger.calypso.neu.http; | 
 |  |  |  | 
 |  |  | import com.sun.net.httpserver.HttpContext; | 
 |  |  | import com.sun.net.httpserver.HttpExchange; | 
 |  |  | import de.uhilger.calypso.neu.actor.PlayActor; | 
 |  |  | import de.uhilger.calypso.neu.actor.ShellActor; | 
 |  |  | import de.uhilger.calypso.neu.actor.StopServerActor; | 
 |  |  | import java.io.IOException; | 
 |  |  | import java.util.Collection; | 
 |  |  | import java.util.Map; | 
 |  |  | import java.util.logging.Level; | 
 |  |  | import java.util.logging.Logger; | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * Der ApiHandler ist die proprietaer fuer Calypso vorgesehene  | 
 |  |  |  * Weise, HTTP-Anfragen zu verarbeiten. | 
 |  |  |  * | 
 |  |  |  * Der ApiHandler ist ein HttpHandler, der vom Server ueber die  | 
 |  |  |  * gesamte Laufzeit des Programmes hinweg gehalten wird. Deswegen  | 
 |  |  |  * beschraenkt sich der Programmcode des ApiHandlers auf die | 
 |  |  |  * Verteilung der Kommandos auf Aktoren. Die Aktoren werden je nach  | 
 |  |  |  * Bedarf instanziiert und enthalten zusammengenommen den Code  | 
 |  |  |  * des Anwendungskerns. | 
 |  |  |  * | 
 |  |  |  * @author Ulrich Hilger | 
 |  |  |  */ | 
 |  |  | public class ApiHandler extends HttpApi { | 
 |  |  |  | 
 |  |  |   public static final int PLAYERCMD = 2; | 
 |  |  |   public static final int SERVERCMD = 3; | 
 |  |  |   public static final String PLAY = "play"; | 
 |  |  |   public static final String PAUSE = "pause"; | 
 |  |  |   public static final String SEEK = "seek"; | 
 |  |  |   public static final String STOP = "stop"; | 
 |  |  |   public static final String SERVER = "server"; | 
 |  |  |   public static final String PING = "ping"; | 
 |  |  |    | 
 |  |  |   public static final String PLAYER = "player-proc"; | 
 |  |  |    | 
 |  |  |   /** | 
 |  |  |    * Die HTTP-Anfrage ausführen | 
 |  |  |    *  | 
 |  |  |    * WICHTIG: Die HTTP-Parameter aus dem Query-Teil muessen in  | 
 |  |  |    * der Reihenfolge sein, die vom Shell-Skript erwartet wird.  | 
 |  |  |    * | 
 |  |  |    * @param elems die Elemente des URI-Pfads | 
 |  |  |    * @param parameter die Parameter des Query-Teils der URI | 
 |  |  |    * @param exchange das Objekt mit Infos zum HTTP-Aufruf | 
 |  |  |    * @return die Antwort, die gesendet werden soll | 
 |  |  |    */ | 
 |  |  |   @Override | 
 |  |  |   protected String process(String[] elems, Map parameter, HttpExchange exchange) { | 
 |  |  |     String antwort; | 
 |  |  |     Object o; | 
 |  |  |  | 
 |  |  |     switch (elems[PLAYERCMD]) { | 
 |  |  |       case PLAY: | 
 |  |  |         PlayActor a = new PlayActor(); | 
 |  |  |         o = a.run(parameter); | 
 |  |  |         if (o instanceof Process) { | 
 |  |  |           exchange.getHttpContext().getAttributes().put(PLAYER, o); | 
 |  |  |           antwort = "Abspielprozess gestartet."; | 
 |  |  |         } else { | 
 |  |  |           // kein Prozess, es ist etwas schief gelaufen | 
 |  |  |           antwort = "Abspielen konnte nicht gestartet werden."; | 
 |  |  |         } | 
 |  |  |         break; | 
 |  |  |  | 
 |  |  |       case PAUSE: | 
 |  |  |       case SEEK: | 
 |  |  |         ShellActor pa = new ShellActor();       | 
 |  |  |         try { | 
 |  |  |           pa.run(getSkriptDir(exchange), elems[PLAYERCMD], toShellParams(parameter)); | 
 |  |  |           antwort = elems[PLAYERCMD] + " ausgefuehrt"; | 
 |  |  |         } catch (IOException ex) { | 
 |  |  |           Logger.getLogger(ApiHandler.class.getName()).log(Level.SEVERE, null, ex); | 
 |  |  |           antwort = "Fehler bei der Ausfuehrung von " + elems[PLAYERCMD]; | 
 |  |  |         }         | 
 |  |  |         break; | 
 |  |  |  | 
 |  |  |       case STOP: | 
 |  |  |         o = exchange.getHttpContext().getAttributes().get(PLAYER); | 
 |  |  |         if(o instanceof Process) { | 
 |  |  |           Process p = (Process) o; | 
 |  |  |           p.destroy(); | 
 |  |  |           antwort = "Player gestoppt."; | 
 |  |  |         } else { | 
 |  |  |           antwort = "Es wurde kein Player-Prozess zum Stoppen gefunden."; | 
 |  |  |         } | 
 |  |  |         break; | 
 |  |  |          | 
 |  |  |       case SERVER: | 
 |  |  |         if(elems[SERVERCMD].equals(STOP)) { | 
 |  |  |           new StopServerActor().run(exchange.getHttpContext()); | 
 |  |  |           antwort = "Calypso: Der Server wird angehalten und die App beendet."; | 
 |  |  |         } else { | 
 |  |  |           antwort = elems[SERVERCMD] + " ist ein unbekanntes Serverkommando"; | 
 |  |  |         } | 
 |  |  |         break; | 
 |  |  |          | 
 |  |  |       case PING: | 
 |  |  |         antwort = elems[PLAYERCMD]; | 
 |  |  |         break; | 
 |  |  |          | 
 |  |  |       default: | 
 |  |  |         antwort = "Kommando " + elems[PLAYERCMD] + " unbekannt"; | 
 |  |  |         break; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return antwort; | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |   private String[] toShellParams(Map parameter) { | 
 |  |  |     Collection<String> c = parameter.values(); | 
 |  |  |     return c.toArray(String[]::new); | 
 |  |  |   } | 
 |  |  |  | 
 |  |  |   private String getSkriptDir(HttpExchange exchange) { | 
 |  |  |     HttpContext context = exchange.getHttpContext(); | 
 |  |  |     Object o = context.getAttributes().get(Server.SKRIPT_DIR); | 
 |  |  |     if (o instanceof String) { | 
 |  |  |       return (String) o; | 
 |  |  |     } else { | 
 |  |  |       // kein Skript-Dir | 
 |  |  |       return ""; | 
 |  |  |     } | 
 |  |  |   } | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  | package de.uhilger.calypso.neu.http; | 
 |  |  |  | 
 |  |  | import com.sun.net.httpserver.HttpExchange; | 
 |  |  | import com.sun.net.httpserver.HttpHandler; | 
 |  |  | import java.io.IOException; | 
 |  |  | import java.io.OutputStream; | 
 |  |  | import java.util.HashMap; | 
 |  |  | import java.util.Map; | 
 |  |  | import java.util.Set; | 
 |  |  | import java.util.logging.Level; | 
 |  |  | import java.util.logging.Logger; | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * Die Klasse HttpApi verwendet die von der Klasse HttpExchange  | 
 |  |  |  * gelieferten Elemente einer HTTP-Anfrage und leitet sie an  | 
 |  |  |  * die abstrakte Methode process, wo Subklassen das jeweilige  | 
 |  |  |  * Kommando aus der HTTP-Anfrage ausfuehren können. | 
 |  |  |  *  | 
 |  |  |  * @author Ulrich Hilger | 
 |  |  |  */ | 
 |  |  | public abstract class HttpApi implements HttpHandler { | 
 |  |  |  | 
 |  |  |   public static final String AMP = "&"; | 
 |  |  |   public static final String SLASH = "/"; | 
 |  |  |  | 
 |  |  |   @Override | 
 |  |  |   public void handle(HttpExchange exchange) throws IOException { | 
 |  |  |     String path = exchange.getRequestURI().getPath(); | 
 |  |  |     String[] elems = path.split(SLASH); | 
 |  |  |     Map params = getQueryMap(exchange); | 
 |  |  |     String antwort = process(elems, params, exchange); | 
 |  |  |     antwortSenden(exchange, params, path, antwort); | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |   /** | 
 |  |  |    * Eine HTTP-Anfrage ausführen | 
 |  |  |    *  | 
 |  |  |    * @param elems die Elemente des URI-Pfads | 
 |  |  |    * @param parameter die Parameter des Query-Teils der URI | 
 |  |  |    * @return die Antwort, die gesendet werden soll | 
 |  |  |    */ | 
 |  |  |   protected abstract String process(String[] elems, Map parameter, HttpExchange exchange); | 
 |  |  |  | 
 |  |  |   /* | 
 |  |  |     Den Query-Teil einer URL in die Parameter zerlegen | 
 |  |  |    | 
 |  |  |     Die Zerlegung erfolgt mit String.split nach  | 
 |  |  |     & und dann nach = | 
 |  |  |   */ | 
 |  |  |   protected Map getQueryMap(HttpExchange t) { | 
 |  |  |     HashMap map = new HashMap(); | 
 |  |  |     String query = t.getRequestURI().getQuery(); | 
 |  |  |     if(query != null && query.length() > 0) { | 
 |  |  |       String qParts[] = query.split("&"); | 
 |  |  |       for(String qPart : qParts) { | 
 |  |  |         Logger logger = Logger.getLogger(de.uhilger.calypso.neu.http.HttpApi.class.getName()); | 
 |  |  |         logger.log(Level.FINER, "qPart: {0}", qPart); | 
 |  |  |         String pParts[] = qPart.split("="); | 
 |  |  |         map.put(pParts[0], pParts[1]); | 
 |  |  |         logger.log(Level.FINER, "pParts[0]: {0} pParts[1]: {1}", new Object[]{pParts[0], pParts[1]}); | 
 |  |  |       } | 
 |  |  |     } | 
 |  |  |     return map; | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |   protected void antwortSenden(HttpExchange exchange, Map params, String cmd, String antwort) throws IOException { | 
 |  |  |     String httpResponseStr = getResponseString(params, cmd, antwort); | 
 |  |  |     sendResponse(exchange, httpResponseStr);       | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |   protected String getResponseString(Map map, String cmd, String antwort) { | 
 |  |  |     Set keys = map.keySet(); | 
 |  |  |     StringBuilder buf = new StringBuilder(); | 
 |  |  |     buf.append(cmd); | 
 |  |  |     buf.append(System.lineSeparator()); | 
 |  |  |     keys.forEach((Object key) -> { | 
 |  |  |       buf.append("key: "); | 
 |  |  |       buf.append(key); | 
 |  |  |       buf.append(System.lineSeparator()); | 
 |  |  |       buf.append("value: ");  | 
 |  |  |       buf.append(map.get(key)); | 
 |  |  |       buf.append(System.lineSeparator()); | 
 |  |  |       //logger.log(Level.FINE, "key {0} value {1}", new Object[]{key, map.get(key)}); | 
 |  |  |     }); | 
 |  |  |     buf.append(antwort); | 
 |  |  |     return buf.toString(); | 
 |  |  |   }     | 
 |  |  |  | 
 |  |  |   protected void sendResponse(HttpExchange t, String response) throws IOException { | 
 |  |  |     t.sendResponseHeaders(200, response.length()); | 
 |  |  |     OutputStream os = t.getResponseBody(); | 
 |  |  |     os.write(response.getBytes()); | 
 |  |  |     os.close();     | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |    | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |     Calypso - Media Player Remote Control via HTTP for Raspberry Pi | 
 |  |  |     Copyright (C) 2021-2023  Ulrich Hilger | 
 |  |  |  | 
 |  |  |     This program is free software: you can redistribute it and/or modify | 
 |  |  |     it under the terms of the GNU Affero General Public License as | 
 |  |  |     published by the Free Software Foundation, either version 3 of the | 
 |  |  |     License, or (at your option) any later version. | 
 |  |  |  | 
 |  |  |     This program 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 Affero General Public License for more details. | 
 |  |  |  | 
 |  |  |     You should have received a copy of the GNU Affero General Public License | 
 |  |  |     along with this program.  If not, see <https://www.gnu.org/licenses/>. | 
 |  |  | */ | 
 |  |  |  | 
 |  |  | package de.uhilger.calypso.neu.http; | 
 |  |  |  | 
 |  |  | import com.sun.net.httpserver.HttpContext; | 
 |  |  | import com.sun.net.httpserver.HttpServer; | 
 |  |  | import de.uhilger.calypso.neu.AppProperties; | 
 |  |  | import java.io.File; | 
 |  |  | import java.io.IOException; | 
 |  |  | import java.net.InetSocketAddress; | 
 |  |  | import java.util.concurrent.Executors; | 
 |  |  | import java.util.logging.Level; | 
 |  |  | import java.util.logging.Logger; | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * | 
 |  |  |  * @author Ulrich Hilger | 
 |  |  |  */ | 
 |  |  | public class Server { | 
 |  |  |    | 
 |  |  |   //private static final Logger logger = Logger.getLogger(de.uhilger.calypso.Server.class.getName()); | 
 |  |  |    | 
 |  |  |   public static final String CONF = "conf"; | 
 |  |  |   public static final String PORT = "port"; | 
 |  |  |   public static final String SKRIPTE = "skripte"; | 
 |  |  |   public static final String CTX = "ctx"; | 
 |  |  |   public static final String EQUAL = "="; | 
 |  |  |   public static final String BLANK = " "; | 
 |  |  |   public static final String SKRIPT_DIR = "skript-dir"; | 
 |  |  |    | 
 |  |  |  | 
 |  |  |   public void start(AppProperties einst) { | 
 |  |  |     Logger logger = Logger.getLogger(de.uhilger.calypso.neu.http.Server.class.getName()); | 
 |  |  |     logger.log(Level.INFO, "Server startet auf Port {0}", einst.getString(PORT)); | 
 |  |  |     String skripte = einst.getString(SKRIPTE); | 
 |  |  |     File skript = new File(skripte, "pause"); | 
 |  |  |     if(skript.exists()) { | 
 |  |  |       logger.log(Level.INFO, "Skripte gefunden in {0}", skript.getParentFile().getAbsolutePath()); | 
 |  |  |     } else { | 
 |  |  |       logger.log(Level.INFO, "Skripte nicht gefunden, Ordner laut Einstellungen: {0}", skripte); | 
 |  |  |     } | 
 |  |  |     try { | 
 |  |  |       HttpServer server = HttpServer.create(new InetSocketAddress(einst.getInt(PORT)), 0); | 
 |  |  |       String ctx = einst.getString(CTX); | 
 |  |  |       HttpContext context = server.createContext(ctx, new ApiHandler()); | 
 |  |  |       context.getAttributes().put(SKRIPT_DIR, einst.getString(SKRIPTE)); | 
 |  |  |       server.setExecutor(Executors.newFixedThreadPool(20)); | 
 |  |  |       server.start(); | 
 |  |  |     } catch (IOException ex) { | 
 |  |  |       logger.log(Level.SEVERE, null, ex); | 
 |  |  |     }     | 
 |  |  |   } | 
 |  |  | } |