Support for reloading config from disk, writing config to disk after 60 seconds of no changes, reloading config from disk on API call request
This commit is contained in:
parent
cac86fff57
commit
132e59db51
8 changed files with 339 additions and 222 deletions
15
lib/comms.h
15
lib/comms.h
|
@ -3,21 +3,6 @@
|
|||
#include "shared_memory.h"
|
||||
#include "util.h"
|
||||
|
||||
#define COMM_STATUS_SOURCE 0x80
|
||||
#define COMM_STATUS_DONOTTRACK 0x40
|
||||
#define COMM_STATUS_DISCONNECT 0x20
|
||||
#define COMM_STATUS_REQDISCONNECT 0x10
|
||||
#define COMM_STATUS_ACTIVE 0x1
|
||||
#define COMM_STATUS_INVALID 0x0
|
||||
#define SESS_BUNDLE_DEFAULT_VIEWER 14
|
||||
#define SESS_BUNDLE_DEFAULT_OTHER 15
|
||||
#define SESS_DEFAULT_STREAM_INFO_MODE 1
|
||||
#define SESS_HTTP_AS_VIEWER 1
|
||||
#define SESS_HTTP_AS_OUTPUT 2
|
||||
#define SESS_HTTP_DISABLED 3
|
||||
#define SESS_HTTP_AS_UNSPECIFIED 4
|
||||
#define SESS_TKN_DEFAULT_MODE 15
|
||||
|
||||
|
||||
#define COMM_LOOP(comm, onActive, onDisconnect) \
|
||||
{\
|
||||
|
|
|
@ -285,3 +285,20 @@ static inline void show_stackframe(){}
|
|||
|
||||
#define NEW_TRACK_ID 0x80000000
|
||||
#define QUICK_NEGOTIATE 0xC0000000
|
||||
|
||||
// Session and Comm library related constants
|
||||
#define COMM_STATUS_SOURCE 0x80
|
||||
#define COMM_STATUS_DONOTTRACK 0x40
|
||||
#define COMM_STATUS_DISCONNECT 0x20
|
||||
#define COMM_STATUS_REQDISCONNECT 0x10
|
||||
#define COMM_STATUS_ACTIVE 0x1
|
||||
#define COMM_STATUS_INVALID 0x0
|
||||
#define SESS_BUNDLE_DEFAULT_VIEWER 14
|
||||
#define SESS_BUNDLE_DEFAULT_OTHER 15
|
||||
#define SESS_DEFAULT_STREAM_INFO_MODE 1
|
||||
#define SESS_HTTP_AS_VIEWER 1
|
||||
#define SESS_HTTP_AS_OUTPUT 2
|
||||
#define SESS_HTTP_DISABLED 3
|
||||
#define SESS_HTTP_AS_UNSPECIFIED 4
|
||||
#define SESS_TKN_DEFAULT_MODE 15
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <sys/time.h> //for gettimeofday
|
||||
#include <sys/stat.h>
|
||||
#include <time.h> //for time and nanosleep
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
|
@ -166,3 +167,13 @@ std::string Util::getDateString(uint64_t epoch){
|
|||
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S %z", timeinfo);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
/// Gets unix time of last file modification, or 0 if this information is not available for any reason
|
||||
uint64_t Util::getFileUnixTime(const std::string & filename){
|
||||
struct stat fInfo;
|
||||
if (stat(filename.c_str(), &fInfo) == 0){
|
||||
return fInfo.st_mtime;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,4 +21,5 @@ namespace Util{
|
|||
uint64_t getMSFromUTCString(std::string UTCString);
|
||||
uint64_t getUTCTimeDiff(std::string UTCString, uint64_t epochMillis);
|
||||
std::string getDateString(uint64_t epoch = 0);
|
||||
uint64_t getFileUnixTime(const std::string & filename);
|
||||
}// namespace Util
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
#define COMPILED_PASSWORD ""
|
||||
#endif
|
||||
|
||||
uint64_t lastConfRead = 0;
|
||||
|
||||
/// the following function is a simple check if the user wants to proceed to fix (y), ignore (n) or
|
||||
/// abort on (a) a question
|
||||
static inline char yna(std::string &user_input){
|
||||
|
@ -72,12 +74,49 @@ void createAccount(std::string account){
|
|||
}
|
||||
|
||||
/// Status monitoring thread.
|
||||
/// Will check outputs, inputs and converters every three seconds
|
||||
/// Checks status of "protocols" (listening outputs)
|
||||
/// Updates config from disk when changed
|
||||
/// Writes config to disk after some time of no changes
|
||||
void statusMonitor(void *np){
|
||||
Controller::loadActiveConnectors();
|
||||
while (Controller::conf.is_active){
|
||||
// this scope prevents the configMutex from being locked constantly
|
||||
|
||||
// Check configuration file last changed time
|
||||
uint64_t confTime = Util::getFileUnixTime(Controller::conf.getString("configFile"));
|
||||
if (Controller::Storage.isMember("config_split")){
|
||||
jsonForEach(Controller::Storage["config_split"], cs){
|
||||
if (cs->isString()){
|
||||
uint64_t subTime = Util::getFileUnixTime(cs->asStringRef());
|
||||
if (subTime && subTime > confTime){confTime = subTime;}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we recently wrote, assume we know the contents since that time, too.
|
||||
if (lastConfRead < Controller::lastConfigWrite){lastConfRead = Controller::lastConfigWrite;}
|
||||
// If the config has changed, update Controller::lastConfigChange
|
||||
{
|
||||
JSON::Value currConfig;
|
||||
Controller::getConfigAsWritten(currConfig);
|
||||
if (Controller::lastConfigSeen != currConfig){
|
||||
Controller::lastConfigChange = Util::epoch();
|
||||
Controller::lastConfigSeen = currConfig;
|
||||
}
|
||||
}
|
||||
// Read from disk if they are newer than our last read
|
||||
if (confTime && confTime > lastConfRead){
|
||||
INFO_MSG("Configuration files changed - reloading configuration from disk");
|
||||
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
|
||||
Controller::readConfigFromDisk();
|
||||
lastConfRead = Controller::lastConfigChange;
|
||||
}
|
||||
// Write to disk if we have made no changes in the last 60 seconds and the files are older than the last change
|
||||
if (Controller::lastConfigChange > Controller::lastConfigWrite && Controller::lastConfigChange < Util::epoch() - 60){
|
||||
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
|
||||
Controller::writeConfigToDisk();
|
||||
if (lastConfRead < Controller::lastConfigWrite){lastConfRead = Controller::lastConfigWrite;}
|
||||
}
|
||||
|
||||
{ // this scope prevents the configMutex from being locked constantly
|
||||
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
|
||||
// checks online protocols, reports changes to status
|
||||
if (Controller::CheckProtocols(Controller::Storage["config"]["protocols"], Controller::capabilities)){
|
||||
|
@ -86,7 +125,8 @@ void statusMonitor(void *np){
|
|||
// checks stream statuses, reports changes to status
|
||||
Controller::CheckAllStreams(Controller::Storage["streams"]);
|
||||
}
|
||||
Util::sleep(3000); // wait at least 3 seconds
|
||||
|
||||
Util::sleep(3000); // wait at most 3 seconds
|
||||
}
|
||||
if (Util::Config::is_restarting){
|
||||
Controller::prepareActiveConnectorsForReload();
|
||||
|
@ -137,6 +177,75 @@ void handleUSR1Parent(int signum, siginfo_t *sigInfo, void *ignore){
|
|||
Util::Config::is_restarting = true;
|
||||
}
|
||||
|
||||
bool interactiveFirstTimeSetup(){
|
||||
// check for username
|
||||
if (!Controller::Storage.isMember("account") || Controller::Storage["account"].size() < 1){
|
||||
std::string in_string = "";
|
||||
while (yna(in_string) == 'x' && Controller::conf.is_active){
|
||||
std::cout << "Account not set, do you want to create an account? (y)es, (n)o, (a)bort: ";
|
||||
std::cout.flush();
|
||||
std::getline(std::cin, in_string);
|
||||
switch (yna(in_string)){
|
||||
case 'y':{
|
||||
// create account
|
||||
std::string usr_string = "";
|
||||
while (!(Controller::Storage.isMember("account") && Controller::Storage["account"].size() > 0) &&
|
||||
Controller::conf.is_active){
|
||||
std::cout << "Please type in the username, a colon and a password in the following "
|
||||
"format; username:password"
|
||||
<< std::endl
|
||||
<< ": ";
|
||||
std::cout.flush();
|
||||
std::getline(std::cin, usr_string);
|
||||
createAccount(usr_string);
|
||||
}
|
||||
}break;
|
||||
case 'a': return false; // abort bootup
|
||||
case 't':{
|
||||
createAccount("test:test");
|
||||
if ((Controller::capabilities["connectors"].size()) &&
|
||||
(!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
|
||||
Controller::Storage["config"]["protocols"].size() < 1)){
|
||||
// create protocols
|
||||
jsonForEach(Controller::capabilities["connectors"], it){
|
||||
if (!it->isMember("required")){
|
||||
JSON::Value newProtocol;
|
||||
newProtocol["connector"] = it.key();
|
||||
Controller::Storage["config"]["protocols"].append(newProtocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// check for protocols
|
||||
if ((Controller::capabilities["connectors"].size()) &&
|
||||
(!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
|
||||
Controller::Storage["config"]["protocols"].size() < 1)){
|
||||
std::string in_string = "";
|
||||
while (yna(in_string) == 'x' && Controller::conf.is_active){
|
||||
std::cout << "Protocols not set, do you want to enable default protocols? (y)es, (n)o, (a)bort: ";
|
||||
std::cout.flush();
|
||||
std::getline(std::cin, in_string);
|
||||
if (yna(in_string) == 'y'){
|
||||
// create protocols
|
||||
jsonForEach(Controller::capabilities["connectors"], it){
|
||||
if (!it->isMember("required")){
|
||||
JSON::Value newProtocol;
|
||||
newProtocol["connector"] = it.key();
|
||||
Controller::Storage["config"]["protocols"].append(newProtocol);
|
||||
}
|
||||
}
|
||||
}else if (yna(in_string) == 'a'){
|
||||
// abort controller startup
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///\brief The main loop for the controller.
|
||||
int main_loop(int argc, char **argv){
|
||||
{
|
||||
|
@ -167,11 +276,6 @@ int main_loop(int argc, char **argv){
|
|||
Controller::conf.addOption("port", stored_port);
|
||||
Controller::conf.addOption("interface", stored_interface);
|
||||
Controller::conf.addOption("username", stored_user);
|
||||
Controller::conf.addOption(
|
||||
"maxconnsperip",
|
||||
JSON::fromString("{\"long\":\"maxconnsperip\", \"short\":\"M\", \"arg\":\"integer\" "
|
||||
"\"default\":0, \"help\":\"Max simultaneous sessions per unique IP address. "
|
||||
"Only enforced if the USER_NEW trigger is in use.\"}"));
|
||||
Controller::conf.addOption(
|
||||
"account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" "
|
||||
"\"default\":\"\", \"help\":\"A username:password string to "
|
||||
|
@ -234,8 +338,6 @@ int main_loop(int argc, char **argv){
|
|||
<< "!----" APPNAME " Started at " << buffer << " ----!" << std::endl;
|
||||
}
|
||||
}
|
||||
// reload config from config file
|
||||
Controller::Storage = JSON::fromFile(Controller::conf.getString("configFile"));
|
||||
|
||||
{// spawn thread that reads stderr of process
|
||||
std::string logPipe = Util::getTmpFolder() + "MstLog";
|
||||
|
@ -279,80 +381,7 @@ int main_loop(int argc, char **argv){
|
|||
setenv("MIST_CONTROL", "1", 0); // Signal in the environment that the controller handles all children
|
||||
}
|
||||
|
||||
if (Controller::Storage.isMember("config_split")){
|
||||
jsonForEach(Controller::Storage["config_split"], cs){
|
||||
if (cs->isString()){
|
||||
JSON::Value tmpConf = JSON::fromFile(cs->asStringRef());
|
||||
if (tmpConf.isMember(cs.key())){
|
||||
INFO_MSG("Loading '%s' section of config from file %s", cs.key().c_str(), cs->asStringRef().c_str());
|
||||
Controller::Storage[cs.key()] = tmpConf[cs.key()];
|
||||
}else{
|
||||
WARN_MSG("There is no '%s' section in file %s; skipping load", cs.key().c_str(), cs->asStringRef().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set default delay before retry
|
||||
if (!Controller::Storage.isMember("push_settings")){
|
||||
Controller::Storage["push_settings"]["wait"] = 3;
|
||||
Controller::Storage["push_settings"]["maxspeed"] = 0;
|
||||
}
|
||||
if (Controller::conf.getOption("debug", true).size() > 1){
|
||||
Controller::Storage["config"]["debug"] = Controller::conf.getInteger("debug");
|
||||
}
|
||||
if (Controller::Storage.isMember("config") && Controller::Storage["config"].isMember("debug") &&
|
||||
Controller::Storage["config"]["debug"].isInt()){
|
||||
Util::printDebugLevel = Controller::Storage["config"]["debug"].asInt();
|
||||
}
|
||||
// check for port, interface and username in arguments
|
||||
// if they are not there, take them from config file, if there
|
||||
if (Controller::Storage["config"]["controller"]["port"]){
|
||||
Controller::conf.getOption("port", true)[0u] =
|
||||
Controller::Storage["config"]["controller"]["port"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"]["interface"]){
|
||||
Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"]["username"]){
|
||||
Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"].isMember("prometheus")){
|
||||
if (Controller::Storage["config"]["controller"]["prometheus"]){
|
||||
Controller::Storage["config"]["prometheus"] =
|
||||
Controller::Storage["config"]["controller"]["prometheus"];
|
||||
}
|
||||
Controller::Storage["config"]["controller"].removeMember("prometheus");
|
||||
}
|
||||
if (Controller::Storage["config"]["prometheus"]){
|
||||
Controller::conf.getOption("prometheus", true)[0u] =
|
||||
Controller::Storage["config"]["prometheus"];
|
||||
}
|
||||
if (Controller::Storage["config"].isMember("accesslog")){
|
||||
Controller::conf.getOption("accesslog", true)[0u] = Controller::Storage["config"]["accesslog"];
|
||||
}
|
||||
Controller::Storage["config"]["prometheus"] = Controller::conf.getString("prometheus");
|
||||
Controller::Storage["config"]["accesslog"] = Controller::conf.getString("accesslog");
|
||||
Controller::normalizeTrustedProxies(Controller::Storage["config"]["trustedproxy"]);
|
||||
if (!Controller::Storage["config"]["sessionViewerMode"]){
|
||||
Controller::Storage["config"]["sessionViewerMode"] = SESS_BUNDLE_DEFAULT_VIEWER;
|
||||
}
|
||||
if (!Controller::Storage["config"]["sessionInputMode"]){
|
||||
Controller::Storage["config"]["sessionInputMode"] = SESS_BUNDLE_DEFAULT_OTHER;
|
||||
}
|
||||
if (!Controller::Storage["config"]["sessionOutputMode"]){
|
||||
Controller::Storage["config"]["sessionOutputMode"] = SESS_BUNDLE_DEFAULT_OTHER;
|
||||
}
|
||||
if (!Controller::Storage["config"]["sessionUnspecifiedMode"]){
|
||||
Controller::Storage["config"]["sessionUnspecifiedMode"] = 0;
|
||||
}
|
||||
if (!Controller::Storage["config"]["sessionStreamInfoMode"]){
|
||||
Controller::Storage["config"]["sessionStreamInfoMode"] = SESS_DEFAULT_STREAM_INFO_MODE;
|
||||
}
|
||||
if (!Controller::Storage["config"].isMember("tknMode")){
|
||||
Controller::Storage["config"]["tknMode"] = SESS_TKN_DEFAULT_MODE;
|
||||
}
|
||||
Controller::prometheus = Controller::Storage["config"]["prometheus"].asStringRef();
|
||||
Controller::accesslog = Controller::Storage["config"]["accesslog"].asStringRef();
|
||||
Controller::readConfigFromDisk();
|
||||
Controller::writeConfig();
|
||||
if (!Controller::conf.is_active){return 0;}
|
||||
Controller::checkAvailProtocols();
|
||||
|
@ -435,74 +464,9 @@ int main_loop(int argc, char **argv){
|
|||
}
|
||||
#endif
|
||||
|
||||
// if a terminal is connected and we're not logging to file
|
||||
// if a terminal is connected, check for first time setup
|
||||
if (Controller::isTerminal){
|
||||
// check for username
|
||||
if (!Controller::Storage.isMember("account") || Controller::Storage["account"].size() < 1){
|
||||
std::string in_string = "";
|
||||
while (yna(in_string) == 'x' && Controller::conf.is_active){
|
||||
std::cout << "Account not set, do you want to create an account? (y)es, (n)o, (a)bort: ";
|
||||
std::cout.flush();
|
||||
std::getline(std::cin, in_string);
|
||||
switch (yna(in_string)){
|
||||
case 'y':{
|
||||
// create account
|
||||
std::string usr_string = "";
|
||||
while (!(Controller::Storage.isMember("account") && Controller::Storage["account"].size() > 0) &&
|
||||
Controller::conf.is_active){
|
||||
std::cout << "Please type in the username, a colon and a password in the following "
|
||||
"format; username:password"
|
||||
<< std::endl
|
||||
<< ": ";
|
||||
std::cout.flush();
|
||||
std::getline(std::cin, usr_string);
|
||||
createAccount(usr_string);
|
||||
}
|
||||
}break;
|
||||
case 'a': return 0; // abort bootup
|
||||
case 't':{
|
||||
createAccount("test:test");
|
||||
if ((Controller::capabilities["connectors"].size()) &&
|
||||
(!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
|
||||
Controller::Storage["config"]["protocols"].size() < 1)){
|
||||
// create protocols
|
||||
jsonForEach(Controller::capabilities["connectors"], it){
|
||||
if (!it->isMember("required")){
|
||||
JSON::Value newProtocol;
|
||||
newProtocol["connector"] = it.key();
|
||||
Controller::Storage["config"]["protocols"].append(newProtocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// check for protocols
|
||||
if ((Controller::capabilities["connectors"].size()) &&
|
||||
(!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
|
||||
Controller::Storage["config"]["protocols"].size() < 1)){
|
||||
std::string in_string = "";
|
||||
while (yna(in_string) == 'x' && Controller::conf.is_active){
|
||||
std::cout << "Protocols not set, do you want to enable default protocols? (y)es, (n)o, "
|
||||
"(a)bort: ";
|
||||
std::cout.flush();
|
||||
std::getline(std::cin, in_string);
|
||||
if (yna(in_string) == 'y'){
|
||||
// create protocols
|
||||
jsonForEach(Controller::capabilities["connectors"], it){
|
||||
if (!it->isMember("required")){
|
||||
JSON::Value newProtocol;
|
||||
newProtocol["connector"] = it.key();
|
||||
Controller::Storage["config"]["protocols"].append(newProtocol);
|
||||
}
|
||||
}
|
||||
}else if (yna(in_string) == 'a'){
|
||||
// abort controller startup
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!interactiveFirstTimeSetup()){return 0;}
|
||||
}
|
||||
|
||||
// Check if we have a usable server, if not, print messages with helpful hints
|
||||
|
@ -530,39 +494,6 @@ int main_loop(int argc, char **argv){
|
|||
}
|
||||
}
|
||||
|
||||
// Upgrade old configurations
|
||||
{
|
||||
bool foundCMAF = false;
|
||||
bool edit = false;
|
||||
JSON::Value newVal;
|
||||
jsonForEach(Controller::Storage["config"]["protocols"], it){
|
||||
if ((*it)["connector"].asStringRef() == "HSS"){
|
||||
edit = true;
|
||||
continue;
|
||||
}
|
||||
if ((*it)["connector"].asStringRef() == "DASH"){
|
||||
edit = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((*it)["connector"].asStringRef() == "SRT"){
|
||||
edit = true;
|
||||
JSON::Value newSubRip = *it;
|
||||
newSubRip["connector"] = "SubRip";
|
||||
newVal.append(newSubRip);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((*it)["connector"].asStringRef() == "CMAF"){foundCMAF = true;}
|
||||
newVal.append(*it);
|
||||
}
|
||||
if (edit && !foundCMAF){newVal.append(JSON::fromString("{\"connector\":\"CMAF\"}"));}
|
||||
if (edit){
|
||||
Controller::Storage["config"]["protocols"] = newVal;
|
||||
Controller::Log("CONF", "Translated protocols to new versions");
|
||||
}
|
||||
}
|
||||
|
||||
// Generate instanceId once per boot.
|
||||
if (Controller::instanceId == ""){
|
||||
srand(mix(clock(), time(0), getpid()));
|
||||
|
@ -648,7 +579,7 @@ int main_loop(int argc, char **argv){
|
|||
/*LTS-END*/
|
||||
// write config
|
||||
tthread::lock_guard<tthread::mutex> guard(Controller::logMutex);
|
||||
Controller::writeConfigToDisk();
|
||||
Controller::writeConfigToDisk(true);
|
||||
// stop all child processes
|
||||
Util::Procs::StopAll();
|
||||
// give everything some time to print messages
|
||||
|
@ -719,3 +650,4 @@ int main(int argc, char **argv){
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -552,6 +552,11 @@ void Controller::handleAPICommands(JSON::Value &Request, JSON::Value &Response){
|
|||
Response["config_backup"].assignFrom(Controller::Storage, skip);
|
||||
}
|
||||
|
||||
if (Request.isMember("config_reload")){
|
||||
INFO_MSG("Reloading configuration from disk on request");
|
||||
Controller::readConfigFromDisk();
|
||||
}
|
||||
|
||||
if (Request.isMember("config_restore")){
|
||||
std::set<std::string> skip;
|
||||
skip.insert("log");
|
||||
|
@ -1222,11 +1227,10 @@ void Controller::handleAPICommands(JSON::Value &Request, JSON::Value &Response){
|
|||
}
|
||||
|
||||
Controller::writeConfig();
|
||||
Controller::configChanged = false;
|
||||
|
||||
if (Request.isMember("save")){
|
||||
Controller::Log("CONF", "Writing config to file on request through API");
|
||||
Controller::writeConfigToDisk();
|
||||
Controller::writeConfigToDisk(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@ namespace Controller{
|
|||
tthread::mutex configMutex;
|
||||
tthread::mutex logMutex;
|
||||
uint64_t logCounter = 0;
|
||||
bool configChanged = false;
|
||||
uint64_t lastConfigChange = 0;
|
||||
uint64_t lastConfigWrite = 0;
|
||||
bool isTerminal = false;
|
||||
bool isColorized = false;
|
||||
uint32_t maxLogsRecs = 0;
|
||||
|
@ -36,6 +37,10 @@ namespace Controller{
|
|||
Util::RelAccX *rlxStrm = 0;
|
||||
uint64_t systemBoot = Util::unixMS() - Util::bootMS();
|
||||
|
||||
JSON::Value lastConfigWriteAttempt;
|
||||
JSON::Value lastConfigSeen;
|
||||
std::map<std::string, JSON::Value> lastConfigWritten;
|
||||
|
||||
Util::RelAccX *logAccessor(){return rlxLogs;}
|
||||
|
||||
Util::RelAccX *accesslogAccessor(){return rlxAccs;}
|
||||
|
@ -238,37 +243,193 @@ namespace Controller{
|
|||
Util::logParser((long long)err, fileno(stdout), Controller::isColorized, &Log);
|
||||
}
|
||||
|
||||
/// Writes the current config to the location set in the configFile setting.
|
||||
/// On error, prints an error-level message and the config to stdout.
|
||||
void writeConfigToDisk(){
|
||||
JSON::Value tmp;
|
||||
void getConfigAsWritten(JSON::Value & conf){
|
||||
std::set<std::string> skip;
|
||||
skip.insert("log");
|
||||
skip.insert("online");
|
||||
skip.insert("error");
|
||||
tmp.assignFrom(Controller::Storage, skip);
|
||||
conf.assignFrom(Controller::Storage, skip);
|
||||
}
|
||||
|
||||
/// Writes the current config to the location set in the configFile setting.
|
||||
/// On error, prints an error-level message and the config to stdout.
|
||||
void writeConfigToDisk(bool forceWrite){
|
||||
bool success = true;
|
||||
JSON::Value tmp;
|
||||
getConfigAsWritten(tmp);
|
||||
|
||||
// We keep an extra copy temporarily, since we want to keep the "full" config around for comparisons
|
||||
JSON::Value mainConfig = tmp;
|
||||
|
||||
if (Controller::Storage.isMember("config_split")){
|
||||
jsonForEach(Controller::Storage["config_split"], cs){
|
||||
if (cs->isString() && tmp.isMember(cs.key())){
|
||||
// Only (attempt to) write if there was a change since last write success
|
||||
if (!forceWrite && lastConfigWritten[cs.key()] == tmp[cs.key()]){
|
||||
if (cs.key() != "config_split"){mainConfig.removeMember(cs.key());}
|
||||
continue;
|
||||
}
|
||||
JSON::Value tmpConf = JSON::fromFile(cs->asStringRef());
|
||||
tmpConf[cs.key()] = tmp[cs.key()];
|
||||
// Attempt to write this section to the given file
|
||||
if (!Controller::WriteFile(cs->asStringRef(), tmpConf.toString())){
|
||||
success = false;
|
||||
// Only print the error + config data if this is new data since the last write attempt
|
||||
if (tmp[cs.key()] != lastConfigWriteAttempt[cs.key()]){
|
||||
ERROR_MSG("Error writing config.%s to %s", cs.key().c_str(), cs->asStringRef().c_str());
|
||||
std::cout << "**config." << cs.key() <<"**" << std::endl;
|
||||
std::cout << tmp[cs.key()].toString() << std::endl;
|
||||
std::cout << "**End config." << cs.key() << "**" << std::endl;
|
||||
}
|
||||
if (cs.key() != "config_split"){tmp.removeMember(cs.key());}
|
||||
}else{
|
||||
// Log the successfully written data
|
||||
lastConfigWritten[cs.key()] = tmp[cs.key()];
|
||||
}
|
||||
// Remove this section from the to-be-written main config
|
||||
if (cs.key() != "config_split"){mainConfig.removeMember(cs.key());}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only (attempt to) write if there was a change since last write success
|
||||
if (forceWrite || lastConfigWritten[""] != mainConfig){
|
||||
// Attempt to write this section to the given file
|
||||
if (!Controller::WriteFile(Controller::conf.getString("configFile"), tmp.toString())){
|
||||
success = false;
|
||||
// Only print the error + config data if this is new data since the last write attempt
|
||||
if (tmp != lastConfigWriteAttempt){
|
||||
ERROR_MSG("Error writing config to %s", Controller::conf.getString("configFile").c_str());
|
||||
std::cout << "**Config**" << std::endl;
|
||||
std::cout << tmp.toString() << std::endl;
|
||||
std::cout << mainConfig.toString() << std::endl;
|
||||
std::cout << "**End config**" << std::endl;
|
||||
}
|
||||
}else{
|
||||
lastConfigWritten[""] = mainConfig;
|
||||
}
|
||||
}
|
||||
|
||||
if (success){
|
||||
INFO_MSG("Wrote updated configuration to disk");
|
||||
lastConfigWrite = Util::epoch();
|
||||
}
|
||||
lastConfigWriteAttempt = tmp;
|
||||
}
|
||||
|
||||
void readConfigFromDisk(){
|
||||
// reload config from config file
|
||||
Controller::Storage = JSON::fromFile(Controller::conf.getString("configFile"));
|
||||
|
||||
if (Controller::Storage.isMember("config_split")){
|
||||
jsonForEach(Controller::Storage["config_split"], cs){
|
||||
if (cs->isString()){
|
||||
JSON::Value tmpConf = JSON::fromFile(cs->asStringRef());
|
||||
if (tmpConf.isMember(cs.key())){
|
||||
INFO_MSG("Loading '%s' section of config from file %s", cs.key().c_str(), cs->asStringRef().c_str());
|
||||
Controller::Storage[cs.key()] = tmpConf[cs.key()];
|
||||
}else{
|
||||
WARN_MSG("There is no '%s' section in file %s; skipping load", cs.key().c_str(), cs->asStringRef().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set default delay before retry
|
||||
if (!Controller::Storage.isMember("push_settings")){
|
||||
Controller::Storage["push_settings"]["wait"] = 3;
|
||||
Controller::Storage["push_settings"]["maxspeed"] = 0;
|
||||
}
|
||||
if (Controller::conf.getOption("debug", true).size() > 1){
|
||||
Controller::Storage["config"]["debug"] = Controller::conf.getInteger("debug");
|
||||
}
|
||||
if (Controller::Storage.isMember("config") && Controller::Storage["config"].isMember("debug") &&
|
||||
Controller::Storage["config"]["debug"].isInt()){
|
||||
Util::printDebugLevel = Controller::Storage["config"]["debug"].asInt();
|
||||
}
|
||||
// check for port, interface and username in arguments
|
||||
// if they are not there, take them from config file, if there
|
||||
if (Controller::Storage["config"]["controller"]["port"]){
|
||||
Controller::conf.getOption("port", true)[0u] =
|
||||
Controller::Storage["config"]["controller"]["port"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"]["interface"]){
|
||||
Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"]["username"]){
|
||||
Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"].isMember("prometheus")){
|
||||
if (Controller::Storage["config"]["controller"]["prometheus"]){
|
||||
Controller::Storage["config"]["prometheus"] =
|
||||
Controller::Storage["config"]["controller"]["prometheus"];
|
||||
}
|
||||
Controller::Storage["config"]["controller"].removeMember("prometheus");
|
||||
}
|
||||
if (Controller::Storage["config"]["prometheus"]){
|
||||
Controller::conf.getOption("prometheus", true)[0u] =
|
||||
Controller::Storage["config"]["prometheus"];
|
||||
}
|
||||
if (Controller::Storage["config"].isMember("accesslog")){
|
||||
Controller::conf.getOption("accesslog", true)[0u] = Controller::Storage["config"]["accesslog"];
|
||||
}
|
||||
Controller::Storage["config"]["prometheus"] = Controller::conf.getString("prometheus");
|
||||
Controller::Storage["config"]["accesslog"] = Controller::conf.getString("accesslog");
|
||||
Controller::normalizeTrustedProxies(Controller::Storage["config"]["trustedproxy"]);
|
||||
if (!Controller::Storage["config"]["sessionViewerMode"]){
|
||||
Controller::Storage["config"]["sessionViewerMode"] = SESS_BUNDLE_DEFAULT_VIEWER;
|
||||
}
|
||||
if (!Controller::Storage["config"]["sessionInputMode"]){
|
||||
Controller::Storage["config"]["sessionInputMode"] = SESS_BUNDLE_DEFAULT_OTHER;
|
||||
}
|
||||
if (!Controller::Storage["config"]["sessionOutputMode"]){
|
||||
Controller::Storage["config"]["sessionOutputMode"] = SESS_BUNDLE_DEFAULT_OTHER;
|
||||
}
|
||||
if (!Controller::Storage["config"]["sessionUnspecifiedMode"]){
|
||||
Controller::Storage["config"]["sessionUnspecifiedMode"] = 0;
|
||||
}
|
||||
if (!Controller::Storage["config"]["sessionStreamInfoMode"]){
|
||||
Controller::Storage["config"]["sessionStreamInfoMode"] = SESS_DEFAULT_STREAM_INFO_MODE;
|
||||
}
|
||||
if (!Controller::Storage["config"].isMember("tknMode")){
|
||||
Controller::Storage["config"]["tknMode"] = SESS_TKN_DEFAULT_MODE;
|
||||
}
|
||||
Controller::prometheus = Controller::Storage["config"]["prometheus"].asStringRef();
|
||||
Controller::accesslog = Controller::Storage["config"]["accesslog"].asStringRef();
|
||||
|
||||
// Upgrade old configurations
|
||||
{
|
||||
bool foundCMAF = false;
|
||||
bool edit = false;
|
||||
JSON::Value newVal;
|
||||
jsonForEach(Controller::Storage["config"]["protocols"], it){
|
||||
if ((*it)["connector"].asStringRef() == "HSS"){
|
||||
edit = true;
|
||||
continue;
|
||||
}
|
||||
if ((*it)["connector"].asStringRef() == "DASH"){
|
||||
edit = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((*it)["connector"].asStringRef() == "SRT"){
|
||||
edit = true;
|
||||
JSON::Value newSubRip = *it;
|
||||
newSubRip["connector"] = "SubRip";
|
||||
newVal.append(newSubRip);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((*it)["connector"].asStringRef() == "CMAF"){foundCMAF = true;}
|
||||
newVal.append(*it);
|
||||
}
|
||||
if (edit && !foundCMAF){newVal.append(JSON::fromString("{\"connector\":\"CMAF\"}"));}
|
||||
if (edit){
|
||||
Controller::Storage["config"]["protocols"] = newVal;
|
||||
Controller::Log("CONF", "Translated protocols to new versions");
|
||||
}
|
||||
}
|
||||
Controller::lastConfigChange = Controller::lastConfigWrite = Util::epoch();
|
||||
Controller::lastConfigWriteAttempt.null();
|
||||
getConfigAsWritten(Controller::lastConfigWriteAttempt);
|
||||
lastConfigSeen = lastConfigWriteAttempt;
|
||||
}
|
||||
|
||||
void writeCapabilities(){
|
||||
|
|
|
@ -13,11 +13,14 @@ namespace Controller{
|
|||
extern JSON::Value Storage; ///< Global storage of data.
|
||||
extern tthread::mutex logMutex; ///< Mutex for log thread.
|
||||
extern tthread::mutex configMutex; ///< Mutex for server config access.
|
||||
extern bool configChanged; ///< Bool that indicates config must be written to SHM.
|
||||
extern bool isTerminal; ///< True if connected to a terminal and not a log file.
|
||||
extern bool isColorized; ///< True if we colorize the output
|
||||
extern uint64_t logCounter; ///< Count of logged messages since boot
|
||||
extern uint64_t systemBoot; ///< Unix time in milliseconds of system boot
|
||||
extern uint64_t lastConfigChange; ///< Unix time in seconds of last configuration change
|
||||
extern uint64_t lastConfigWrite; ///< Unix time in seconds of last time configuration was written to disk
|
||||
extern JSON::Value lastConfigWriteAttempt; ///< Contents of last attempted config write
|
||||
extern JSON::Value lastConfigSeen; ///< Contents of config last time we looked at it. Used to check for changes.
|
||||
|
||||
Util::RelAccX *logAccessor();
|
||||
Util::RelAccX *accesslogAccessor();
|
||||
|
@ -34,7 +37,10 @@ namespace Controller{
|
|||
|
||||
/// Write contents to Filename.
|
||||
bool WriteFile(std::string Filename, std::string contents);
|
||||
void writeConfigToDisk();
|
||||
|
||||
void getConfigAsWritten(JSON::Value & conf);
|
||||
void writeConfigToDisk(bool forceWrite = false);
|
||||
void readConfigFromDisk();
|
||||
|
||||
void handleMsg(void *err);
|
||||
void initState();
|
||||
|
|
Loading…
Add table
Reference in a new issue