Playlist support
This commit is contained in:
parent
7beea43d31
commit
907be3b1f4
12 changed files with 544 additions and 29 deletions
|
@ -1,5 +1,6 @@
|
|||
#include <dirent.h> //for browse API call
|
||||
#include <sys/stat.h> //for browse API call
|
||||
#include <fstream>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/stream.h>
|
||||
|
@ -31,6 +32,59 @@ std::string getChallenge(Socket::Connection & conn){
|
|||
return Secure::md5(Date.str().c_str() + conn.getHost());
|
||||
}
|
||||
|
||||
/// Executes a single Playlist-based API command. Recurses if necessary.
|
||||
static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines){
|
||||
if (!cmd.isArray() || !cmd.size()){
|
||||
FAIL_MSG("Not a valid playlist API command: %s", cmd.toString().c_str());
|
||||
return;
|
||||
}
|
||||
if (cmd[0u].isArray()){
|
||||
jsonForEach(cmd, it){
|
||||
executePlsCommand(*it, lines);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!cmd[0u].isString()){
|
||||
FAIL_MSG("Not a valid playlist API command: %s", cmd.toString().c_str());
|
||||
return;
|
||||
}
|
||||
if (cmd[0u].asStringRef() == "append" && cmd.size() == 2 && cmd[1u].isString()){
|
||||
lines.push_back(cmd[1u].asStringRef());
|
||||
return;
|
||||
}
|
||||
if (cmd[0u].asStringRef() == "clear" && cmd.size() == 1){
|
||||
lines.clear();
|
||||
return;
|
||||
}
|
||||
if (cmd[0u].asStringRef() == "remove" && cmd.size() == 2 && cmd[1u].isString()){
|
||||
const std::string & toRemove = cmd[1u].asStringRef();
|
||||
for (std::deque<std::string>::iterator it = lines.begin(); it != lines.end(); ++it){
|
||||
if ((*it) == toRemove){
|
||||
(*it) = "";
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (cmd[0u].asStringRef() == "line" && cmd.size() == 3 && cmd[1u].isInt() && cmd[2u].isString()){
|
||||
if (cmd[1u].asInt() >= lines.size()){
|
||||
FAIL_MSG("Line number %d does not exist in playlist - cannot modify line", (int)cmd[1u].asInt());
|
||||
return;
|
||||
}
|
||||
lines[cmd[1u].asInt()] = cmd[2u].asStringRef();
|
||||
return;
|
||||
}
|
||||
if (cmd[0u].asStringRef() == "replace" && cmd.size() == 3 && cmd[1u].isString() && cmd[2u].isString()){
|
||||
const std::string & toReplace = cmd[1u].asStringRef();
|
||||
for (std::deque<std::string>::iterator it = lines.begin(); it != lines.end(); ++it){
|
||||
if ((*it) == toReplace){
|
||||
(*it) = cmd[2u].asStringRef();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
FAIL_MSG("Not a valid playlist API command: %s", cmd.toString().c_str());
|
||||
}
|
||||
|
||||
///\brief Checks an authorization request for a given user.
|
||||
///\param Request The request to be parsed.
|
||||
///\param Response The location to store the generated response.
|
||||
|
@ -704,6 +758,78 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
free(rpath);
|
||||
}
|
||||
|
||||
// Examples of valid playlist requests:
|
||||
//"playlist":{"streamname": ["append", "path/to/file.ts"]}
|
||||
//"playlist":{"streamname": ["remove", "path/to/file.ts"]}
|
||||
//"playlist":{"streamname": ["line", 2, "path/to/file.ts"]}
|
||||
//"playlist":{"streamname": true}
|
||||
//"playlist":{"streamname": [["append", "path/to/file.ts"], ["remove", "path/to/file.ts"]]}
|
||||
if (Request.isMember("playlist")){
|
||||
if (!Request["playlist"].isObject()){
|
||||
ERROR_MSG("Playlist API call requires object payload, no object given");
|
||||
}else{
|
||||
jsonForEach(Request["playlist"], it){
|
||||
if (!Controller::Storage["streams"].isMember(it.key()) || !Controller::Storage["streams"][it.key()].isMember("source")){
|
||||
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' not configured", it.key().c_str());
|
||||
}else{
|
||||
std::string src = Controller::Storage["streams"][it.key()]["source"].asString();
|
||||
if (src.substr(src.size() - 4) != ".pls"){
|
||||
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' is not playlist-based", it.key().c_str());
|
||||
}else{
|
||||
bool readFirst = true;
|
||||
struct stat fileinfo;
|
||||
if (stat(src.c_str(), &fileinfo) != 0){
|
||||
if (errno == EACCES){
|
||||
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' playlist '%s' cannot be accessed (no file permissions)", it.key().c_str(), src.c_str());
|
||||
break;
|
||||
}
|
||||
if (errno == ENOENT){
|
||||
WARN_MSG("Creating playlist file: %s", src.c_str());
|
||||
readFirst = false;
|
||||
}
|
||||
}
|
||||
std::deque<std::string> lines;
|
||||
if (readFirst){
|
||||
std::ifstream plsRead(src.c_str());
|
||||
if (!plsRead.good()){
|
||||
FAIL_MSG("Playlist (%s) for stream '%s' could not be opened for reading; aborting command(s)", src.c_str(), it.key().c_str());
|
||||
break;
|
||||
}
|
||||
std::string line;
|
||||
do {
|
||||
std::getline(plsRead, line);
|
||||
if (line.size() || plsRead.good()){lines.push_back(line);}
|
||||
} while(plsRead.good());
|
||||
}
|
||||
unsigned int plsNo = 0;
|
||||
for (std::deque<std::string>::iterator plsIt = lines.begin(); plsIt != lines.end(); ++plsIt){
|
||||
MEDIUM_MSG("Before playlist command item %u: %s", plsNo, plsIt->c_str());
|
||||
++plsNo;
|
||||
}
|
||||
if (!it->isBool()){
|
||||
executePlsCommand(*it, lines);
|
||||
}
|
||||
JSON::Value & outPls = Response["playlist"][it.key()];
|
||||
std::ofstream plsOutFile(src.c_str(), std::ios_base::trunc);
|
||||
if (!plsOutFile.good()){
|
||||
FAIL_MSG("Could not open playlist for writing: %s", src.c_str());
|
||||
break;
|
||||
}
|
||||
plsNo = 0;
|
||||
for (std::deque<std::string>::iterator plsIt = lines.begin(); plsIt != lines.end(); ++plsIt){
|
||||
MEDIUM_MSG("After playlist command item %u: %s", plsNo, plsIt->c_str());
|
||||
++plsNo;
|
||||
outPls.append(*plsIt);
|
||||
if (plsNo < lines.size() || (*plsIt).size()){
|
||||
plsOutFile << (*plsIt) << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Request.isMember("save")){
|
||||
Controller::Log("CONF", "Writing config to file on request through API");
|
||||
Controller::writeConfigToDisk();
|
||||
|
|
|
@ -115,7 +115,12 @@ namespace Controller {
|
|||
//new style always on
|
||||
if (data.isMember("always_on")){
|
||||
INFO_MSG("Starting always-on input %s: %s", name.c_str(), URL.c_str());
|
||||
Util::startInput(name, URL);
|
||||
std::map<std::string, std::string> empty_overrides;
|
||||
pid_t program = 0;
|
||||
Util::startInput(name, URL, true, false, empty_overrides, &program);
|
||||
if (program){
|
||||
inputProcesses[name] = program;
|
||||
}
|
||||
}
|
||||
//non-VoD stream
|
||||
if (URL.substr(0, 1) != "/"){return;}
|
||||
|
@ -182,6 +187,7 @@ namespace Controller {
|
|||
out[jit.key()] = (*jit);
|
||||
out[jit.key()].removeNullMembers();
|
||||
out[jit.key()]["name"] = jit.key();
|
||||
checkParameters(out[jit.key()]);
|
||||
Log("STRM", std::string("Updated stream ") + jit.key());
|
||||
}
|
||||
}else{
|
||||
|
@ -206,6 +212,7 @@ namespace Controller {
|
|||
out[jit.key()] = (*jit);
|
||||
out[jit.key()].removeNullMembers();
|
||||
out[jit.key()]["name"] = jit.key();
|
||||
checkParameters(out[jit.key()]);
|
||||
Log("STRM", std::string("New stream ") + jit.key());
|
||||
}
|
||||
Controller::writeStream(jit.key(), out[jit.key()]);
|
||||
|
@ -374,5 +381,35 @@ namespace Controller {
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool isMatch(const std::string & source, const std::string & match){
|
||||
std::string front = match.substr(0,match.find('*'));
|
||||
std::string back = match.substr(match.find('*')+1);
|
||||
return (source.substr(0,front.size()) == front && source.substr(source.size()-back.size()) == back);
|
||||
}
|
||||
|
||||
void checkParameters(JSON::Value & streamObj){
|
||||
JSON::Value & inpt = Controller::capabilities["inputs"];
|
||||
std::string match;
|
||||
jsonForEach(inpt, it){
|
||||
if ((*it)["source_match"].isArray()){
|
||||
jsonForEach((*it)["source_match"], subIt){
|
||||
if (isMatch(streamObj["source"].asStringRef(), (*subIt).asStringRef())){
|
||||
match = (*it)["name"].asString();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((*it)["source_match"].isString()){
|
||||
if (isMatch(streamObj["source"].asStringRef(), (*it)["source_match"].asStringRef())){
|
||||
match = (*it)["name"].asString();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (match != ""){
|
||||
jsonForEach(inpt[match]["hardcoded"], it){
|
||||
streamObj[it.key()] = *it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //Controller namespace
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace Controller {
|
|||
void CheckStreams(JSON::Value & in, JSON::Value & out);
|
||||
void AddStreams(JSON::Value & in, JSON::Value & out);
|
||||
int deleteStream(const std::string & name, JSON::Value & out, bool sourceFileToo = false);
|
||||
void checkParameters(JSON::Value & stream);
|
||||
|
||||
struct liveCheck {
|
||||
long long int lastms;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue