/*
Tango - Personal Media Center
Copyright (C) 2021 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 .
*/
package de.uhilger.tango.api;
import com.google.gson.Gson;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import de.uhilger.tango.PlaylistListener;
import de.uhilger.tango.Server;
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.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
/**
* Der ListHandler bearbeitet HTTP-Anfragen zu Abspiellisten
*
* GET /tango/api/alist/[pl-name] die Titel-Objekte der Liste [pl-name] liefern
* GET /tango/api/alist/[pl-name]/m3u eine einfache Playlist im M3U-Format ausgeben
* GET /tango/api/alist/[pl-name]/[nr] den Titel mit der Nummer [nr] abrufen
*
* PUT /tango/api/alist/[pl-name] den Titel im Body anfuegen an die Liste [pl-name]
* PUT /tango/api/alist/[pl-name]/[nr] an der Position nr der Liste [pl-name] den Titel im Body einfuegen
* PUT /tango/api/alist/[pl-name]/[nrVon]/[nrNach] den Titel von seiner aktuellen Position an eine
* andere Position der Liste [pl-name] verschieben
* DELETE /tango/api/alist/[pl-name]/[nr] den Titel an der Position [nr] aus der Liste [pl-name] entfernen
* DELETE /tango/api/alist/[pl-name]/alle alle Titel aus der Liste [pl-name] entfernen
*
* TODO (2.1.2023):
* - Liste ab Titel spielen
* - Ganzes Album der Liste hinzufuegen
*
* @author Ulrich Hilger
* @version 1, 8.4.2021
*/
public class ListHandler extends AbstractHandler {
private static final Logger logger = Logger.getLogger(ListHandler.class.getName());
public static final String ALLE_TITEL = "alle";
private String conf;
private List listeners;
public ListHandler(String conf) {
this.conf = conf;
listeners = new ArrayList();
}
@Override
protected String get(HttpExchange e) {
String path = e.getRequestURI().toString();
String[] elems = path.split(Server.SLASH);
if(elems.length > 5) {
if(elems[5].endsWith("m3u")) {
Headers headers = e.getResponseHeaders();
headers.add("Content-Type", "application/m3u");
return getM3u(e, elems[4]);
} else {
try {
int index = Integer.parseInt(elems[5]);
return getTitel(elems[4], index);
} catch(NumberFormatException ex) {
return "ungueltig";
}
}
} else {
String plname = elems[elems.length - 1];
FileStorage fs = new FileStorage(conf);
String json = fs.readJson(FileStorage.ST_ABSPIELLISTE, plname);
return embedInCustomType(json, FileStorage.ST_ABSPIELLISTE);
}
}
private String getM3u(HttpExchange e, String plname) {
StringBuilder sb = new StringBuilder();
FileStorage fs = new FileStorage(conf);
Entity entity = fs.read(FileStorage.ST_ABSPIELLISTE, plname);
if (entity instanceof Abspielliste) {
Abspielliste liste = (Abspielliste) entity;
List titelListe = liste.getTitel();
for(Titel titel : titelListe) {
String server = getEinstellung(fs,
getResString(MediaSteuerung.RB_HOST), MediaSteuerung.DEFAULT_HOST);
sb.append(server);
sb.append(titel.getKatalogUrl());
sb.append(titel.getPfad());
sb.append(titel.getName());
sb.append(Server.NEWLINE);
}
}
return sb.toString();
}
@Override
protected String put(HttpExchange e) throws IOException {
String path = e.getRequestURI().toString();
String[] elems = path.split(Server.SLASH);
String response = "ListHandler.put: ungueltiger URL";
switch(elems.length) {
case 5: // ohne nr am Ende
response = addTitel(e, elems[4]);
break;
case 6:
response = insertTitel(e, elems[4], Integer.parseInt(elems[5]));
break;
case 7:
response = moveTitel(e, elems[4], Integer.parseInt(elems[5]), Integer.parseInt(elems[6]));
break;
}
return response;
}
private String getTitel(String plname, int index) {
FileStorage fs = new FileStorage(conf);
Entity entity = fs.read(FileStorage.ST_ABSPIELLISTE, plname);
String response = "eom";
if(entity instanceof Abspielliste) {
Abspielliste aliste = (Abspielliste) entity;
//String titelJson = bodyLesen(e);
List titelListe = aliste.getTitel();
if(index < titelListe.size()) {
Titel titel = aliste.getTitel().get(index);
Gson gson = new Gson();
response = gson.toJson(titel);
}
}
return response;
}
/**
* Den Titel im Body von seiner aktuellen Position an die angegebene
* Position setzen. Der Titel an der angegebenen Position rueckt nach
* unten.
*
* Annahme: Die Abspielliste enthaelt keine Titel mehrfach.
*
* @param e
* @param plname
* @param zielPos
* @return
* @throws IOException
*/
private String moveTitel(HttpExchange e, String plname, int pos, int zielPos) throws IOException {
FileStorage fs = new FileStorage(conf);
Entity entity = fs.read(FileStorage.ST_ABSPIELLISTE, plname);
String response = "Titel konnte nicht verschoben werden.";
if(entity instanceof Abspielliste) {
if(pos < zielPos) {
--zielPos;
}
Abspielliste aliste = (Abspielliste) entity;
List liste = aliste.getTitel();
Titel t = liste.get(pos);
liste.remove(pos);
liste.add(zielPos, t);
fs.write(aliste, true);
response = "Titel " + t.getName() + " der Liste " + aliste.getName() +
" an Position " + zielPos + " verschoben.";
}
return response;
}
/**
* Den Titel im Body an Position anPos einfuegen. Der Titel an anPos
* rueckt nach unten.
*
* @param e
* @param plname
* @param anPos
* @return
* @throws IOException
*/
private String insertTitel(HttpExchange e, String plname, int anPos) throws IOException {
FileStorage fs = new FileStorage(conf);
Entity entity = fs.read(FileStorage.ST_ABSPIELLISTE, plname);
String response = "Titel konnte nicht hinzugefuegt werden.";
if(entity instanceof Abspielliste) {
Abspielliste aliste = (Abspielliste) entity;
String titelJson = bodyLesen(e);
Gson gson = new Gson();
Object o = gson.fromJson(titelJson, fs.typeFromName(Titel.class.getSimpleName()).getType());
if(o instanceof Titel) {
Titel titel = (Titel) o;
aliste.putTitel(titel, anPos);
fs.write(aliste, true);
response = "Titel " + titel.getName() + " der Liste " + aliste.getName() +
" an Position " + anPos + " hinzugefuegt.";
}
}
return response;
}
private String addTitel(HttpExchange e, String plname) throws IOException {
FileStorage fs = new FileStorage(conf);
Entity entity = fs.read(FileStorage.ST_ABSPIELLISTE, plname);
String response = "Titel konnte nicht hinzugefuegt werden.";
if(entity instanceof Abspielliste) {
Abspielliste aliste = (Abspielliste) entity;
String titelJson = bodyLesen(e);
Gson gson = new Gson();
Object o = gson.fromJson(titelJson, fs.typeFromName(Titel.class.getSimpleName()).getType());
if(o instanceof Titel) {
Titel titel = (Titel) o;
aliste.addTitel(titel);
fs.write(aliste, true);
response = "Titel " + titel.getName() + " der Liste " + aliste.getName() + " hinzugefuegt.";
}
}
return response;
}
// DELETE /mz/api/alist/[pl-name]/[nr] den Titel an der Position [nr] aus der Liste [pl-name] entfernen
@Override
protected boolean delete(HttpExchange e) {
String path = e.getRequestURI().toString();
String[] elems = path.split(Server.SLASH);
String listName = elems[elems.length - 2];
FileStorage fs = new FileStorage(conf);
Entity entity = fs.read(Abspielliste.class.getSimpleName(), listName);
if(entity instanceof Abspielliste) {
Abspielliste liste = (Abspielliste) entity;
String titelStr = elems[elems.length-1];
if(titelStr.equalsIgnoreCase(ALLE_TITEL)) {
liste.getTitel().clear();
} else {
int idx = Integer.parseInt(elems[elems.length-1]);
liste.getTitel().remove(idx);
sendRemovalInfo(liste.getName(), idx);
}
fs.write(liste, true);
return true;
} else {
return false;
}
}
private void sendRemovalInfo(String listName, int titleIndex) {
Iterator i = listeners.iterator();
while(i.hasNext()) {
PlaylistListener l = i.next();
l.titleRemoved(listName, titleIndex);
}
}
public void addPlaylistListener(PlaylistListener listener) {
this.listeners.add(listener);
}
public void removePlaylistListener(PlaylistListener listener) {
this.listeners.remove(listener);
}
}