diff --git a/src/controller/controller_api.cpp b/src/controller/controller_api.cpp index 968491da..44ffe8c1 100644 --- a/src/controller/controller_api.cpp +++ b/src/controller/controller_api.cpp @@ -511,6 +511,81 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response } } } + if (Request.isMember("deletestreamsource")){ + //if array, delete all elements + //if object, delete all entries + //if string, delete just the one + if (Request["deletestreamsource"].isString()){ + switch (Controller::deleteStream(Request["deletestreamsource"].asStringRef(), Controller::Storage["streams"], true)){ + case 0: + Response["deletestreamsource"] = "0: No action taken"; + break; + case 1: + Response["deletestreamsource"] = "1: Source file deleted"; + break; + case 2: + Response["deletestreamsource"] = "2: Source file and dtsh deleted"; + break; + case -1: + Response["deletestreamsource"] = "-1: Stream deleted, source remains"; + break; + case -2: + Response["deletestreamsource"] = "-2: Stream and source file deleted"; + break; + case -3: + Response["deletestreamsource"] = "-3: Stream, source file and dtsh deleted"; + break; + } + } + if (Request["deletestreamsource"].isArray()){ + jsonForEach(Request["deletestreamsource"], it){ + switch (Controller::deleteStream(it->asStringRef(), Controller::Storage["streams"], true)){ + case 0: + Response["deletestreamsource"][it.num()] = "0: No action taken"; + break; + case 1: + Response["deletestreamsource"][it.num()] = "1: Source file deleted"; + break; + case 2: + Response["deletestreamsource"][it.num()] = "2: Source file and dtsh deleted"; + break; + case -1: + Response["deletestreamsource"][it.num()] = "-1: Stream deleted, source remains"; + break; + case -2: + Response["deletestreamsource"][it.num()] = "-2: Stream and source file deleted"; + break; + case -3: + Response["deletestreamsource"][it.num()] = "-3: Stream, source file and dtsh deleted"; + break; + } + } + } + if (Request["deletestreamsource"].isObject()){ + jsonForEach(Request["deletestreamsource"], it){ + switch (Controller::deleteStream(it.key(), Controller::Storage["streams"], true)){ + case 0: + Response["deletestreamsource"][it.key()] = "0: No action taken"; + break; + case 1: + Response["deletestreamsource"][it.key()] = "1: Source file deleted"; + break; + case 2: + Response["deletestreamsource"][it.key()] = "2: Source file and dtsh deleted"; + break; + case -1: + Response["deletestreamsource"][it.key()] = "-1: Stream deleted, source remains"; + break; + case -2: + Response["deletestreamsource"][it.key()] = "-2: Stream and source file deleted"; + break; + case -3: + Response["deletestreamsource"][it.key()] = "-3: Stream, source file and dtsh deleted"; + break; + } + } + } + } if (Request.isMember("addprotocol")){ if (Request["addprotocol"].isArray()){ jsonForEach(Request["addprotocol"], it){ diff --git a/src/controller/controller_streams.cpp b/src/controller/controller_streams.cpp index 3900e0ad..54860ca1 100644 --- a/src/controller/controller_streams.cpp +++ b/src/controller/controller_streams.cpp @@ -293,24 +293,75 @@ namespace Controller { } - /// \triggers - /// The `"STREAM_REMOVE"` trigger is stream-specific, and is ran whenever a stream is removed from the server configuration. If cancelled, the stream is not removed. Its payload is: - /// ~~~~~~~~~~~~~~~ - /// streamname - /// ~~~~~~~~~~~~~~~ - void deleteStream(const std::string & name, JSON::Value & out) { + /// Deletes the stream (name) from the config (out), optionally also deleting the VoD source file if sourceFileToo is true. + int deleteStream(const std::string & name, JSON::Value & out, bool sourceFileToo) { + int ret = 0; + if (sourceFileToo){ + std::string cleaned = name; + Util::sanitizeName(cleaned); + std::string strmSource; + if (Util::getStreamStatus(cleaned) != STRMSTAT_OFF){ + DTSC::Meta mData = Util::getStreamMeta(cleaned); + if (mData.sourceURI.size()){ + strmSource = mData.sourceURI; + } + } + if (!strmSource.size()){ + std::string smp = cleaned.substr(0, cleaned.find_first_of("+ ")); + if (out.isMember(smp) && out[smp].isMember("source")){ + strmSource = out[smp]["source"].asStringRef(); + } + } + bool noFile = false; + if (strmSource.size()){ + std::string prevInput; + while (true){ + std::string oldSrc = strmSource; + JSON::Value inputCapa = Util::getInputBySource(oldSrc, true); + if (inputCapa["name"].asStringRef() == prevInput){break;} + prevInput = inputCapa["name"].asStringRef(); + strmSource = inputCapa["source_file"].asStringRef(); + if (!strmSource.size()){ + noFile = true; + break; + } + Util::streamVariables(strmSource, cleaned, oldSrc); + } + } + if (noFile){ + WARN_MSG("Not deleting source for stream %s, since the stream does not have an unambiguous source file.", cleaned.c_str()); + }else{ + Util::streamVariables(strmSource, cleaned); + if (!strmSource.size()){ + FAIL_MSG("Could not delete source for stream %s: unable to detect stream source URI using any method", cleaned.c_str()); + }else{ + if (unlink(strmSource.c_str())){ + FAIL_MSG("Could not delete source %s for %s: %s (%d)", strmSource.c_str(), cleaned.c_str(), strerror(errno), errno); + }else{ + ++ret; + Log("STRM", "Deleting source file for stream "+cleaned+": "+strmSource); + //Delete dtsh, ignore failures + if (!unlink((strmSource+".dtsh").c_str())){ + ++ret; + } + } + } + } + } if (!out.isMember(name)){ - return; + return ret; } /*LTS-START*/ if(Triggers::shouldTrigger("STREAM_REMOVE")){ if (!Triggers::doTrigger("STREAM_REMOVE", name, name)){ - return; + return ret; } } /*LTS-END*/ - Log("STRM", std::string("Deleted stream ") + name); + Log("STRM", "Deleted stream " + name); out.removeMember(name); + ++ret; + ret *= -1; if (inputProcesses.count(name)){ pid_t procId = inputProcesses[name]; if (Util::Procs::isRunning(procId)){ @@ -318,6 +369,7 @@ namespace Controller { } inputProcesses.erase(name); } + return ret; } } //Controller namespace diff --git a/src/controller/controller_streams.h b/src/controller/controller_streams.h index 7ef29c92..de961ab0 100644 --- a/src/controller/controller_streams.h +++ b/src/controller/controller_streams.h @@ -6,7 +6,7 @@ namespace Controller { bool CheckAllStreams(JSON::Value & data); void CheckStreams(JSON::Value & in, JSON::Value & out); void AddStreams(JSON::Value & in, JSON::Value & out); - void deleteStream(const std::string & name, JSON::Value & out); + int deleteStream(const std::string & name, JSON::Value & out, bool sourceFileToo = false); struct liveCheck { long long int lastms; diff --git a/src/input/input_av.cpp b/src/input/input_av.cpp index 72ebb882..76d23063 100644 --- a/src/input/input_av.cpp +++ b/src/input/input_av.cpp @@ -16,6 +16,7 @@ namespace Mist { capa["name"] = "AV"; capa["desc"] = "This input uses libavformat to read any type of file. Unfortunately this input cannot be redistributed, but it is a great tool for testing the other file-based inputs against."; capa["source_match"] = "/*"; + capa["source_file"] = "$source"; capa["priority"] = 1ll; capa["codecs"][0u][0u].null(); capa["codecs"][0u][1u].null(); diff --git a/src/input/input_dtsc.cpp b/src/input/input_dtsc.cpp index 1c3e22a8..f5ac66ae 100644 --- a/src/input/input_dtsc.cpp +++ b/src/input/input_dtsc.cpp @@ -19,6 +19,7 @@ namespace Mist { capa["priority"] = 9ll; capa["source_match"].append("/*.dtsc"); capa["source_match"].append("dtsc://*"); + capa["source_file"] = "$source"; capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H263"); capa["codecs"][0u][0u].append("VP6"); diff --git a/src/input/input_ebml.cpp b/src/input/input_ebml.cpp index 85c3ba2e..c6e170d8 100644 --- a/src/input/input_ebml.cpp +++ b/src/input/input_ebml.cpp @@ -12,6 +12,7 @@ namespace Mist{ capa["source_match"].append("/*.mk3d"); capa["source_match"].append("/*.mks"); capa["source_match"].append("/*.webm"); + capa["source_file"] = "$source"; capa["priority"] = 9ll; capa["codecs"].append("H264"); capa["codecs"].append("HEVC"); diff --git a/src/input/input_flv.cpp b/src/input/input_flv.cpp index d9a2264f..803b594d 100644 --- a/src/input/input_flv.cpp +++ b/src/input/input_flv.cpp @@ -19,6 +19,7 @@ namespace Mist { capa["name"] = "FLV"; capa["desc"] = "Allows loading FLV files for Video on Demand."; capa["source_match"] = "/*.flv"; + capa["source_file"] = "$source"; capa["priority"] = 9ll; capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H263"); diff --git a/src/input/input_folder.cpp b/src/input/input_folder.cpp index 1cff0fb9..214c69b4 100644 --- a/src/input/input_folder.cpp +++ b/src/input/input_folder.cpp @@ -11,6 +11,7 @@ namespace Mist { capa["name"] = "Folder"; capa["desc"] = "The folder input will make available all supported files in the given folder as streams under this stream name, in the format STREAMNAME+FILENAME. For example, if your stream is called 'files' and you have a file called 'movie.flv', you could access this file streamed as 'files+movie.flv'. This input does not support subdirectories. To support more complex libraries, look into the documentation for the STREAM_SOURCE trigger."; capa["source_match"] = "/*/"; + capa["source_file"] = "$source/$wildcard"; capa["priority"] = 9ll; capa["morphic"] = 1ll; } diff --git a/src/input/input_mp3.cpp b/src/input/input_mp3.cpp index 43fc4f21..31c66bd3 100644 --- a/src/input/input_mp3.cpp +++ b/src/input/input_mp3.cpp @@ -17,6 +17,7 @@ namespace Mist { capa["name"] = "MP3"; capa["desc"] = "This input allows you to stream MP3 Video on Demand files."; capa["source_match"] = "/*.mp3"; + capa["source_file"] = "$source"; capa["priority"] = 9ll; capa["codecs"][0u][0u].append("MP3"); timestamp = 0; diff --git a/src/input/input_mp4.cpp b/src/input/input_mp4.cpp index 2fe7d846..5fff3c09 100644 --- a/src/input/input_mp4.cpp +++ b/src/input/input_mp4.cpp @@ -160,6 +160,7 @@ namespace Mist{ capa["name"] = "MP4"; capa["desc"] = "This input allows streaming of MP4 files as Video on Demand."; capa["source_match"] = "/*.mp4"; + capa["source_file"] = "$source"; capa["priority"] = 9ll; capa["codecs"][0u][0u].append("HEVC"); capa["codecs"][0u][0u].append("H264"); diff --git a/src/input/input_ogg.cpp b/src/input/input_ogg.cpp index 27158c25..ef897deb 100644 --- a/src/input/input_ogg.cpp +++ b/src/input/input_ogg.cpp @@ -48,6 +48,7 @@ namespace Mist { capa["name"] = "OGG"; capa["desc"] = "This input allows streaming of OGG files as Video on Demand."; capa["source_match"] = "/*.ogg"; + capa["source_file"] = "$source"; capa["codecs"][0u][0u].append("theora"); capa["codecs"][0u][1u].append("vorbis"); capa["codecs"][0u][1u].append("opus"); diff --git a/src/input/input_ts.cpp b/src/input/input_ts.cpp index 48446968..f51e3dc7 100755 --- a/src/input/input_ts.cpp +++ b/src/input/input_ts.cpp @@ -113,6 +113,7 @@ namespace Mist { capa["name"] = "TS"; capa["desc"] = "This input allows you to stream MPEG2-TS data from static files (/*.ts), streamed files or named pipes (stream://*.ts), streamed over HTTP (http://*.ts, http-ts://*), standard input (ts-exec:*), or multicast/unicast UDP sockets (tsudp://*)."; capa["source_match"].append("/*.ts"); + capa["source_file"] = "$source"; capa["source_match"].append("stream://*.ts"); capa["source_match"].append("tsudp://*"); capa["source_match"].append("ts-exec:*");