Restyle
This commit is contained in:
parent
5b79f296d6
commit
fccf66fba2
280 changed files with 56975 additions and 71885 deletions
|
@ -1,7 +1,6 @@
|
|||
/// \file controller.cpp
|
||||
/// Contains all code for the controller executable.
|
||||
|
||||
#include <mist/util.h>
|
||||
#include "controller_api.h"
|
||||
#include "controller_capabilities.h"
|
||||
#include "controller_connectors.h"
|
||||
|
@ -10,6 +9,7 @@
|
|||
#include "controller_storage.h"
|
||||
#include "controller_streams.h"
|
||||
#include <ctime>
|
||||
#include <fstream> //for ram space check
|
||||
#include <iostream>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/config.h>
|
||||
|
@ -21,12 +21,12 @@
|
|||
#include <mist/stream.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/tinythread.h>
|
||||
#include <mist/util.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h> //for shm space check
|
||||
#include <sys/wait.h>
|
||||
#include <vector>
|
||||
#include <sys/statvfs.h> //for shm space check
|
||||
#include <fstream> //for ram space check
|
||||
/*LTS-START*/
|
||||
#include "controller_license.h"
|
||||
#include "controller_limits.h"
|
||||
|
@ -79,8 +79,7 @@ void statusMonitor(void *np){
|
|||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
|
||||
// checks online protocols, reports changes to status
|
||||
if (Controller::CheckProtocols(Controller::Storage["config"]["protocols"],
|
||||
Controller::capabilities)){
|
||||
if (Controller::CheckProtocols(Controller::Storage["config"]["protocols"], Controller::capabilities)){
|
||||
Controller::writeProtocols();
|
||||
}
|
||||
// checks stream statuses, reports changes to status
|
||||
|
@ -181,16 +180,12 @@ int main_loop(int argc, char **argv){
|
|||
"uplink",
|
||||
JSON::fromString("{\"default\":\"\", \"arg\":\"string\", \"help\":\"MistSteward uplink host "
|
||||
"and port.\", \"short\":\"U\", \"long\":\"uplink\"}")); /*LTS*/
|
||||
Controller::conf.addOption("uplink-name",
|
||||
JSON::fromString("{\"default\":\"" COMPILED_USERNAME
|
||||
"\", \"arg\":\"string\", \"help\":\"MistSteward "
|
||||
"uplink username.\", \"short\":\"N\", "
|
||||
"\"long\":\"uplink-name\"}")); /*LTS*/
|
||||
Controller::conf.addOption("uplink-pass",
|
||||
JSON::fromString("{\"default\":\"" COMPILED_PASSWORD
|
||||
"\", \"arg\":\"string\", \"help\":\"MistSteward "
|
||||
"uplink password.\", \"short\":\"P\", "
|
||||
"\"long\":\"uplink-pass\"}")); /*LTS*/
|
||||
Controller::conf.addOption("uplink-name", JSON::fromString("{\"default\":\"" COMPILED_USERNAME "\", \"arg\":\"string\", \"help\":\"MistSteward "
|
||||
"uplink username.\", \"short\":\"N\", "
|
||||
"\"long\":\"uplink-name\"}")); /*LTS*/
|
||||
Controller::conf.addOption("uplink-pass", JSON::fromString("{\"default\":\"" COMPILED_PASSWORD "\", \"arg\":\"string\", \"help\":\"MistSteward "
|
||||
"uplink password.\", \"short\":\"P\", "
|
||||
"\"long\":\"uplink-pass\"}")); /*LTS*/
|
||||
Controller::conf.addOption(
|
||||
"prometheus",
|
||||
JSON::fromString("{\"long\":\"prometheus\", \"short\":\"S\", \"arg\":\"string\" "
|
||||
|
@ -199,8 +194,7 @@ int main_loop(int argc, char **argv){
|
|||
Controller::conf.parseArgs(argc, argv);
|
||||
if (Controller::conf.getString("logfile") != ""){
|
||||
// open logfile, dup stdout to logfile
|
||||
int output =
|
||||
open(Controller::conf.getString("logfile").c_str(), O_APPEND | O_CREAT | O_WRONLY, S_IRWXU);
|
||||
int output = open(Controller::conf.getString("logfile").c_str(), O_APPEND | O_CREAT | O_WRONLY, S_IRWXU);
|
||||
if (output < 0){
|
||||
DEBUG_MSG(DLVL_ERROR, "Could not redirect output to %s: %s",
|
||||
Controller::conf.getString("logfile").c_str(), strerror(errno));
|
||||
|
@ -225,7 +219,7 @@ int main_loop(int argc, char **argv){
|
|||
Controller::Storage = JSON::fromFile(Controller::conf.getString("configFile"));
|
||||
|
||||
{// spawn thread that reads stderr of process
|
||||
std::string logPipe = Util::getTmpFolder()+"MstLog";
|
||||
std::string logPipe = Util::getTmpFolder() + "MstLog";
|
||||
if (mkfifo(logPipe.c_str(), S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH) != 0){
|
||||
if (errno != EEXIST){
|
||||
ERROR_MSG("Could not create log message pipe %s: %s", logPipe.c_str(), strerror(errno));
|
||||
|
@ -233,34 +227,37 @@ int main_loop(int argc, char **argv){
|
|||
}
|
||||
int inFD = -1;
|
||||
if ((inFD = open(logPipe.c_str(), O_RDONLY | O_NONBLOCK)) == -1){
|
||||
ERROR_MSG("Could not open log message pipe %s: %s; falling back to unnamed pipe", logPipe.c_str(), strerror(errno));
|
||||
ERROR_MSG("Could not open log message pipe %s: %s; falling back to unnamed pipe",
|
||||
logPipe.c_str(), strerror(errno));
|
||||
int pipeErr[2];
|
||||
if (pipe(pipeErr) >= 0){
|
||||
dup2(pipeErr[1], STDERR_FILENO); // cause stderr to write to the pipe
|
||||
close(pipeErr[1]); // close the unneeded pipe file descriptor
|
||||
//Start reading log messages from the unnamed pipe
|
||||
Util::Procs::socketList.insert(pipeErr[0]); //Mark this FD as needing to be closed before forking
|
||||
// Start reading log messages from the unnamed pipe
|
||||
Util::Procs::socketList.insert(pipeErr[0]); // Mark this FD as needing to be closed before forking
|
||||
tthread::thread msghandler(Controller::handleMsg, (void *)(((char *)0) + pipeErr[0]));
|
||||
msghandler.detach();
|
||||
}
|
||||
}else{
|
||||
//Set the read end to blocking mode
|
||||
// Set the read end to blocking mode
|
||||
int inFDflags = fcntl(inFD, F_GETFL, 0);
|
||||
fcntl(inFD, F_SETFL, inFDflags & (~O_NONBLOCK));
|
||||
//Start reading log messages from the named pipe
|
||||
Util::Procs::socketList.insert(inFD); //Mark this FD as needing to be closed before forking
|
||||
// Start reading log messages from the named pipe
|
||||
Util::Procs::socketList.insert(inFD); // Mark this FD as needing to be closed before forking
|
||||
tthread::thread msghandler(Controller::handleMsg, (void *)(((char *)0) + inFD));
|
||||
msghandler.detach();
|
||||
//Attempt to open and redirect log messages to named pipe
|
||||
// Attempt to open and redirect log messages to named pipe
|
||||
int outFD = -1;
|
||||
if ((outFD = open(logPipe.c_str(), O_WRONLY)) == -1){
|
||||
ERROR_MSG("Could not open log message pipe %s for writing! %s; falling back to standard error", logPipe.c_str(), strerror(errno));
|
||||
ERROR_MSG(
|
||||
"Could not open log message pipe %s for writing! %s; falling back to standard error",
|
||||
logPipe.c_str(), strerror(errno));
|
||||
}else{
|
||||
dup2(outFD, STDERR_FILENO); // cause stderr to write to the pipe
|
||||
close(outFD); // close the unneeded pipe file descriptor
|
||||
}
|
||||
}
|
||||
setenv("MIST_CONTROL", "1", 0);//Signal in the environment that the controller handles all children
|
||||
setenv("MIST_CONTROL", "1", 0); // Signal in the environment that the controller handles all children
|
||||
}
|
||||
|
||||
if (Controller::conf.getOption("debug", true).size() > 1){
|
||||
|
@ -277,12 +274,10 @@ int main_loop(int argc, char **argv){
|
|||
Controller::Storage["config"]["controller"]["port"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"]["interface"]){
|
||||
Controller::conf.getOption("interface", true)[0u] =
|
||||
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"];
|
||||
Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"].isMember("prometheus")){
|
||||
if (Controller::Storage["config"]["controller"]["prometheus"]){
|
||||
|
@ -313,7 +308,6 @@ int main_loop(int argc, char **argv){
|
|||
createAccount(Controller::conf.getString("account"));
|
||||
Controller::conf.activate(); // activate early, so threads aren't killed.
|
||||
|
||||
|
||||
#if !defined(__CYGWIN__) && !defined(_WIN32)
|
||||
{
|
||||
uint64_t mem_total = 0, mem_free = 0, mem_bufcache = 0, shm_total = 0, shm_free = 0;
|
||||
|
@ -323,8 +317,8 @@ int main_loop(int argc, char **argv){
|
|||
while (meminfo.good()){
|
||||
meminfo.getline(line, 300);
|
||||
if (meminfo.fail()){
|
||||
//empty lines? ignore them, clear flags, continue
|
||||
if ( !meminfo.eof()){
|
||||
// empty lines? ignore them, clear flags, continue
|
||||
if (!meminfo.eof()){
|
||||
meminfo.ignore();
|
||||
meminfo.clear();
|
||||
}
|
||||
|
@ -341,26 +335,47 @@ int main_loop(int argc, char **argv){
|
|||
IPC::sharedPage tmpCapa(SHM_CAPA, DEFAULT_CONF_PAGE_SIZE, false, false);
|
||||
if (tmpCapa.mapped && tmpCapa.handle){
|
||||
fstatvfs(tmpCapa.handle, &shmd);
|
||||
shm_free = (shmd.f_bfree*shmd.f_frsize)/1024;
|
||||
shm_total = (shmd.f_blocks*shmd.f_frsize)/1024;
|
||||
shm_free = (shmd.f_bfree * shmd.f_frsize) / 1024;
|
||||
shm_total = (shmd.f_blocks * shmd.f_frsize) / 1024;
|
||||
}
|
||||
|
||||
if (mem_free+mem_bufcache < 1024*1024){
|
||||
WARN_MSG("You have very little free RAM available (%" PRIu64 " MiB). While Mist will run just fine with this amount, do note that random crashes may occur should you ever run out of free RAM. Please be pro-active and keep an eye on the RAM usage!");
|
||||
if (mem_free + mem_bufcache < 1024 * 1024){
|
||||
WARN_MSG("You have very little free RAM available (%" PRIu64
|
||||
" MiB). While Mist will run just fine with this amount, do note that random crashes "
|
||||
"may occur should you ever run out of free RAM. Please be pro-active and keep an "
|
||||
"eye on the RAM usage!");
|
||||
}
|
||||
if (shm_free < 1024*1024 && mem_total > 1024*1024*1.12){
|
||||
WARN_MSG("You have very little shared memory available (%" PRIu64 " MiB). Mist heavily relies on shared memory: please ensure your shared memory is set to a high value, preferably ~95%% of your total available RAM.", shm_free/1024);
|
||||
if (shm_free < 1024 * 1024 && mem_total > 1024 * 1024 * 1.12){
|
||||
WARN_MSG("You have very little shared memory available (%" PRIu64
|
||||
" MiB). Mist heavily relies on shared memory: please ensure your shared memory is "
|
||||
"set to a high value, preferably ~95%% of your total available RAM.",
|
||||
shm_free / 1024);
|
||||
if (shm_total == 65536){
|
||||
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64 "m` parameter to your `docker run` command to fix this.", (uint64_t)(mem_total*0.95/1024));
|
||||
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64
|
||||
"m` parameter to your `docker run` command to fix this.",
|
||||
(uint64_t)(mem_total * 0.95 / 1024));
|
||||
}else{
|
||||
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o remount,size=%" PRIu64 "m /dev/shm` as root. Doing this automatically every boot depends on your distribution: please check your distro's documentation for instructions.", (uint64_t)(mem_total*0.95/1024));
|
||||
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o "
|
||||
"remount,size=%" PRIu64
|
||||
"m /dev/shm` as root. Doing this automatically every boot depends on your "
|
||||
"distribution: please check your distro's documentation for instructions.",
|
||||
(uint64_t)(mem_total * 0.95 / 1024));
|
||||
}
|
||||
}else if (shm_total <= mem_total/2){
|
||||
WARN_MSG("Your shared memory is half or less of your RAM (%" PRIu64 " / %" PRIu64 " MiB). Mist heavily relies on shared memory: please ensure your shared memory is set to a high value, preferably ~95%% of your total available RAM.", shm_total/1024, mem_total/1024);
|
||||
}else if (shm_total <= mem_total / 2){
|
||||
WARN_MSG("Your shared memory is half or less of your RAM (%" PRIu64 " / %" PRIu64
|
||||
" MiB). Mist heavily relies on shared memory: please ensure your shared memory is "
|
||||
"set to a high value, preferably ~95%% of your total available RAM.",
|
||||
shm_total / 1024, mem_total / 1024);
|
||||
if (shm_total == 65536){
|
||||
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64 "m` parameter to your `docker run` command to fix this.", (uint64_t)(mem_total*0.95/1024));
|
||||
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64
|
||||
"m` parameter to your `docker run` command to fix this.",
|
||||
(uint64_t)(mem_total * 0.95 / 1024));
|
||||
}else{
|
||||
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o remount,size=%" PRIu64 "m /dev/shm` as root. Doing this automatically every boot depends on your distribution: please check your distro's documentation for instructions.", (uint64_t)(mem_total*0.95/1024));
|
||||
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o "
|
||||
"remount,size=%" PRIu64
|
||||
"m /dev/shm` as root. Doing this automatically every boot depends on your "
|
||||
"distribution: please check your distro's documentation for instructions.",
|
||||
(uint64_t)(mem_total * 0.95 / 1024));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -379,8 +394,7 @@ int main_loop(int argc, char **argv){
|
|||
case 'y':{
|
||||
// create account
|
||||
std::string usr_string = "";
|
||||
while (!(Controller::Storage.isMember("account") &&
|
||||
Controller::Storage["account"].size() > 0) &&
|
||||
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"
|
||||
|
@ -391,13 +405,11 @@ int main_loop(int argc, char **argv){
|
|||
createAccount(usr_string);
|
||||
}
|
||||
}break;
|
||||
case 'a':
|
||||
return 0; // abort bootup
|
||||
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.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
|
||||
Controller::Storage["config"]["protocols"].size() < 1)){
|
||||
// create protocols
|
||||
jsonForEach(Controller::capabilities["connectors"], it){
|
||||
|
@ -414,13 +426,12 @@ int main_loop(int argc, char **argv){
|
|||
}
|
||||
// check for protocols
|
||||
if ((Controller::capabilities["connectors"].size()) &&
|
||||
(!Controller::Storage.isMember("config") ||
|
||||
!Controller::Storage["config"].isMember("protocols") ||
|
||||
(!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 << "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'){
|
||||
|
@ -451,20 +462,17 @@ int main_loop(int argc, char **argv){
|
|||
web_port + " and follow the instructions.");
|
||||
}
|
||||
// check for protocols
|
||||
if (!Controller::Storage.isMember("config") ||
|
||||
!Controller::Storage["config"].isMember("protocols") ||
|
||||
if (!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
|
||||
Controller::Storage["config"]["protocols"].size() < 1){
|
||||
Controller::Log(
|
||||
"CONF",
|
||||
"No protocols enabled, remember to set them up through the web interface on port " +
|
||||
web_port + " or API.");
|
||||
Controller::Log("CONF", "No protocols enabled, remember to set them up through the web "
|
||||
"interface on port " +
|
||||
web_port + " or API.");
|
||||
}
|
||||
// check for streams - regardless of logfile setting
|
||||
if (!Controller::Storage.isMember("streams") || Controller::Storage["streams"].size() < 1){
|
||||
Controller::Log(
|
||||
"CONF",
|
||||
"No streams configured, remember to set up streams through the web interface on port " +
|
||||
web_port + " or API.");
|
||||
Controller::Log("CONF", "No streams configured, remember to set up streams through the web "
|
||||
"interface on port " +
|
||||
web_port + " or API.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -646,4 +654,3 @@ int main(int argc, char **argv){
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,48 +1,46 @@
|
|||
#include <dirent.h> //for browse API call
|
||||
#include <sys/stat.h> //for browse API call
|
||||
#include <fstream>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/url.h>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/procs.h>
|
||||
#include <mist/bitfields.h>
|
||||
#include "controller_api.h"
|
||||
#include "controller_capabilities.h"
|
||||
#include "controller_connectors.h"
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_storage.h"
|
||||
#include "controller_streams.h"
|
||||
#include "controller_connectors.h"
|
||||
#include "controller_capabilities.h"
|
||||
#include "controller_statistics.h"
|
||||
#include <dirent.h> //for browse API call
|
||||
#include <fstream>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/bitfields.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/procs.h>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/url.h>
|
||||
#include <sys/stat.h> //for browse API call
|
||||
/*LTS-START*/
|
||||
#include "controller_updater.h"
|
||||
#include "controller_license.h"
|
||||
#include "controller_limits.h"
|
||||
#include "controller_push.h"
|
||||
#include "controller_license.h"
|
||||
#include "controller_updater.h"
|
||||
/*LTS-END*/
|
||||
|
||||
/// Returns the challenge string for authentication, given the socket connection.
|
||||
std::string getChallenge(Socket::Connection & conn){
|
||||
std::string getChallenge(Socket::Connection &conn){
|
||||
time_t Time = time(0);
|
||||
tm tmptime;
|
||||
tm * TimeInfo = localtime_r( &Time, &tmptime);
|
||||
tm *TimeInfo = localtime_r(&Time, &tmptime);
|
||||
std::stringstream Date;
|
||||
Date << TimeInfo->tm_mday << "-" << TimeInfo->tm_mon << "-" << TimeInfo->tm_year + 1900;
|
||||
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){
|
||||
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);
|
||||
}
|
||||
jsonForEach(cmd, it){executePlsCommand(*it, lines);}
|
||||
return;
|
||||
}
|
||||
if (!cmd[0u].isString()){
|
||||
|
@ -58,11 +56,9 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
|
|||
return;
|
||||
}
|
||||
if (cmd[0u].asStringRef() == "remove" && cmd.size() == 2 && cmd[1u].isString()){
|
||||
const std::string & toRemove = cmd[1u].asStringRef();
|
||||
const std::string &toRemove = cmd[1u].asStringRef();
|
||||
for (std::deque<std::string>::iterator it = lines.begin(); it != lines.end(); ++it){
|
||||
if ((*it) == toRemove){
|
||||
(*it) = "";
|
||||
}
|
||||
if ((*it) == toRemove){(*it) = "";}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -75,11 +71,9 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
|
|||
return;
|
||||
}
|
||||
if (cmd[0u].asStringRef() == "replace" && cmd.size() == 3 && cmd[1u].isString() && cmd[2u].isString()){
|
||||
const std::string & toReplace = cmd[1u].asStringRef();
|
||||
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();
|
||||
}
|
||||
if ((*it) == toReplace){(*it) = cmd[2u].asStringRef();}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -93,11 +87,13 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
|
|||
///\return True on successfull authorization, false otherwise.
|
||||
///
|
||||
/// \api
|
||||
/// To login, an `"authorize"` request must be sent. Since HTTP does not use persistent connections, you are required to re-sent authentication with every API request made. To prevent plaintext sending of the password, a random challenge string is sent first, and then the password is hashed together with this challenge string to create a one-time-use string to login with.
|
||||
/// To login, an `"authorize"` request must be sent. Since HTTP does not use persistent connections,
|
||||
/// you are required to re-sent authentication with every API request made. To prevent plaintext sending
|
||||
/// of the password, a random challenge string is sent first, and then the password is hashed together with this challenge string to create a one-time-use string to login with.
|
||||
/// If the user is not authorized, this request is the only request the server will respond to until properly authorized.
|
||||
/// `"authorize"` requests take the form of:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
///{
|
||||
/// //username to login as
|
||||
/// "username": "test",
|
||||
/// //hash of password to login with. Send empty value when no challenge for the hash is known yet.
|
||||
|
@ -105,38 +101,40 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
|
|||
/// // MD5( MD5("secret") + challenge)
|
||||
/// //Where "secret" is the plaintext password.
|
||||
/// "password": ""
|
||||
/// }
|
||||
///}
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// and are responded to as:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
///{
|
||||
/// //current login status. Either "OK", "CHALL", "NOACC" or "ACC_MADE".
|
||||
/// "status": "CHALL",
|
||||
/// //Random value to be used in hashing the password.
|
||||
/// "challenge": "abcdef1234567890"
|
||||
/// }
|
||||
///}
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// The challenge string is sent for all statuses, except `"NOACC"`, where it is left out.
|
||||
/// A status of `"OK"` means you are currently logged in and have access to all other API requests.
|
||||
/// A status of `"CHALL"` means you are not logged in, and a challenge has been provided to login with.
|
||||
/// A status of `"NOACC"` means there are no valid accounts to login with. In this case - and ONLY in this case - it is possible to create a initial login through the API itself. To do so, send a request as follows:
|
||||
/// A status of `"NOACC"` means there are no valid accounts to login with. In this case - and ONLY
|
||||
/// in this case - it is possible to create a initial login through the API itself. To do so, send a request as follows:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
///{
|
||||
/// //username to create, as plain text
|
||||
/// "new_username": "test",
|
||||
/// //password to set, as plain text
|
||||
/// "new_password": "secret"
|
||||
/// }
|
||||
///}
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// Please note that this is NOT secure. At all. Never use this mechanism over a public network!
|
||||
/// A status of `"ACC_MADE"` indicates the account was created successfully and can now be used to login as normal.
|
||||
bool Controller::authorize(JSON::Value & Request, JSON::Value & Response, Socket::Connection & conn){
|
||||
bool Controller::authorize(JSON::Value &Request, JSON::Value &Response, Socket::Connection &conn){
|
||||
std::string Challenge = getChallenge(conn);
|
||||
std::string retval;
|
||||
if (Request.isMember("authorize") && Request["authorize"]["username"].asString() != ""){
|
||||
std::string UserID = Request["authorize"]["username"];
|
||||
if (Storage["account"].isMember(UserID)){
|
||||
if (Secure::md5(Storage["account"][UserID]["password"].asString() + Challenge) == Request["authorize"]["password"].asString()){
|
||||
if (Secure::md5(Storage["account"][UserID]["password"].asString() + Challenge) ==
|
||||
Request["authorize"]["password"].asString()){
|
||||
Response["authorize"]["status"] = "OK";
|
||||
return true;
|
||||
}
|
||||
|
@ -147,57 +145,56 @@ bool Controller::authorize(JSON::Value & Request, JSON::Value & Response, Socket
|
|||
}
|
||||
Response["authorize"]["status"] = "CHALL";
|
||||
Response["authorize"]["challenge"] = Challenge;
|
||||
//the following is used to add the first account through the LSP
|
||||
// the following is used to add the first account through the LSP
|
||||
if (!Storage["account"]){
|
||||
Response["authorize"]["status"] = "NOACC";
|
||||
if (Request["authorize"]["new_username"] && Request["authorize"]["new_password"]){
|
||||
//create account
|
||||
// create account
|
||||
Controller::Log("CONF", "Created account " + Request["authorize"]["new_username"].asString() + " through API");
|
||||
Controller::Storage["account"][Request["authorize"]["new_username"].asString()]["password"] = Secure::md5(Request["authorize"]["new_password"].asString());
|
||||
Controller::Storage["account"][Request["authorize"]["new_username"].asString()]["password"] =
|
||||
Secure::md5(Request["authorize"]["new_password"].asString());
|
||||
Response["authorize"]["status"] = "ACC_MADE";
|
||||
}else{
|
||||
Response["authorize"].removeMember("challenge");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}//Authorize
|
||||
}// Authorize
|
||||
|
||||
class streamStat{
|
||||
public:
|
||||
streamStat(){
|
||||
status = 0;
|
||||
viewers = 0;
|
||||
inputs = 0;
|
||||
outputs = 0;
|
||||
}
|
||||
streamStat(const Util::RelAccX & rlx, uint64_t entry){
|
||||
status = rlx.getInt("status", entry);
|
||||
viewers = rlx.getInt("viewers", entry);
|
||||
inputs = rlx.getInt("inputs", entry);
|
||||
outputs = rlx.getInt("outputs", entry);
|
||||
}
|
||||
bool operator ==(const streamStat &b) const{
|
||||
return (status == b.status && viewers == b.viewers && inputs == b.inputs && outputs == b.outputs);
|
||||
}
|
||||
bool operator !=(const streamStat &b) const{
|
||||
return !(*this == b);
|
||||
}
|
||||
uint8_t status;
|
||||
uint64_t viewers;
|
||||
uint64_t inputs;
|
||||
uint64_t outputs;
|
||||
public:
|
||||
streamStat(){
|
||||
status = 0;
|
||||
viewers = 0;
|
||||
inputs = 0;
|
||||
outputs = 0;
|
||||
}
|
||||
streamStat(const Util::RelAccX &rlx, uint64_t entry){
|
||||
status = rlx.getInt("status", entry);
|
||||
viewers = rlx.getInt("viewers", entry);
|
||||
inputs = rlx.getInt("inputs", entry);
|
||||
outputs = rlx.getInt("outputs", entry);
|
||||
}
|
||||
bool operator==(const streamStat &b) const{
|
||||
return (status == b.status && viewers == b.viewers && inputs == b.inputs && outputs == b.outputs);
|
||||
}
|
||||
bool operator!=(const streamStat &b) const{return !(*this == b);}
|
||||
uint8_t status;
|
||||
uint64_t viewers;
|
||||
uint64_t inputs;
|
||||
uint64_t outputs;
|
||||
};
|
||||
|
||||
void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
|
||||
void Controller::handleWebSocket(HTTP::Parser &H, Socket::Connection &C){
|
||||
std::string logs = H.GetVar("logs");
|
||||
std::string accs = H.GetVar("accs");
|
||||
bool doStreams = H.GetVar("streams").size();
|
||||
HTTP::Websocket W(C, H);
|
||||
if (!W){return;}
|
||||
|
||||
IPC::sharedPage shmLogs(SHM_STATE_LOGS, 1024*1024);
|
||||
IPC::sharedPage shmAccs(SHM_STATE_ACCS, 1024*1024);
|
||||
IPC::sharedPage shmStreams(SHM_STATE_STREAMS, 1024*1024);
|
||||
IPC::sharedPage shmLogs(SHM_STATE_LOGS, 1024 * 1024);
|
||||
IPC::sharedPage shmAccs(SHM_STATE_ACCS, 1024 * 1024);
|
||||
IPC::sharedPage shmStreams(SHM_STATE_STREAMS, 1024 * 1024);
|
||||
Util::RelAccX rlxStreams(shmStreams.mapped);
|
||||
Util::RelAccX rlxLog(shmLogs.mapped);
|
||||
Util::RelAccX rlxAccs(shmAccs.mapped);
|
||||
|
@ -228,7 +225,9 @@ void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
|
|||
if (accs.substr(0, 6) == "since:"){
|
||||
uint64_t startAccs = JSON::Value(accs.substr(6)).asInt();
|
||||
accsPos = rlxAccs.getDeleted();
|
||||
while (accsPos < rlxAccs.getEndPos() && rlxAccs.getInt("time", accsPos) < startAccs){++accsPos;}
|
||||
while (accsPos < rlxAccs.getEndPos() && rlxAccs.getInt("time", accsPos) < startAccs){
|
||||
++accsPos;
|
||||
}
|
||||
}else{
|
||||
uint64_t numAccs = JSON::Value(accs).asInt();
|
||||
if (accsPos <= numAccs){
|
||||
|
@ -270,7 +269,8 @@ void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
|
|||
accsPos++;
|
||||
}
|
||||
if (doStreams){
|
||||
for (std::map<std::string, streamStat>::iterator it = lastStrmStat.begin(); it != lastStrmStat.end(); ++it){
|
||||
for (std::map<std::string, streamStat>::iterator it = lastStrmStat.begin();
|
||||
it != lastStrmStat.end(); ++it){
|
||||
strmRemove.insert(it->first);
|
||||
}
|
||||
uint64_t startPos = rlxStreams.getDeleted();
|
||||
|
@ -307,33 +307,31 @@ void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
|
|||
lastStrmStat.erase(strm);
|
||||
}
|
||||
}
|
||||
if (!sent){
|
||||
Util::sleep(500);
|
||||
}
|
||||
if (!sent){Util::sleep(500);}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles a single incoming API connection.
|
||||
/// Assumes the connection is unauthorized and will allow for 4 requests without authorization before disconnecting.
|
||||
int Controller::handleAPIConnection(Socket::Connection & conn){
|
||||
//set up defaults
|
||||
int Controller::handleAPIConnection(Socket::Connection &conn){
|
||||
// set up defaults
|
||||
unsigned int logins = 0;
|
||||
bool authorized = false;
|
||||
bool isLocal = false;
|
||||
HTTP::Parser H;
|
||||
//while connected and not past login attempt limit
|
||||
// while connected and not past login attempt limit
|
||||
while (conn && logins < 4){
|
||||
if ((conn.spool() || conn.Received().size()) && H.Read(conn)){
|
||||
//Are we local and not forwarded? Instant-authorized.
|
||||
// Are we local and not forwarded? Instant-authorized.
|
||||
if (!authorized && !H.hasHeader("X-Real-IP") && conn.isLocal()){
|
||||
MEDIUM_MSG("Local API access automatically authorized");
|
||||
isLocal = true;
|
||||
authorized = true;
|
||||
}
|
||||
#ifdef NOAUTH
|
||||
//If auth is disabled, always allow access.
|
||||
#ifdef NOAUTH
|
||||
// If auth is disabled, always allow access.
|
||||
authorized = true;
|
||||
#endif
|
||||
#endif
|
||||
if (!authorized && H.hasHeader("Authorization")){
|
||||
std::string auth = H.GetHeader("Authorization");
|
||||
if (auth.substr(0, 5) == "json "){
|
||||
|
@ -347,7 +345,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
H.Clean();
|
||||
H.body = "Please login first or provide a valid token authentication.";
|
||||
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
|
||||
H.SetHeader("WWW-Authenticate", "json "+req["authorize"].toString());
|
||||
H.SetHeader("WWW-Authenticate", "json " + req["authorize"].toString());
|
||||
H.SendResponse("403", "Not authorized", conn);
|
||||
H.Clean();
|
||||
continue;
|
||||
|
@ -355,7 +353,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
}
|
||||
}
|
||||
}
|
||||
//Catch websocket requests
|
||||
// Catch websocket requests
|
||||
if (H.url == "/ws"){
|
||||
if (!authorized){
|
||||
H.Clean();
|
||||
|
@ -369,14 +367,14 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
H.Clean();
|
||||
continue;
|
||||
}
|
||||
//Catch prometheus requests
|
||||
// Catch prometheus requests
|
||||
if (Controller::prometheus.size()){
|
||||
if (H.url == "/"+Controller::prometheus){
|
||||
if (H.url == "/" + Controller::prometheus){
|
||||
handlePrometheus(H, conn, PROMETHEUS_TEXT);
|
||||
H.Clean();
|
||||
continue;
|
||||
}
|
||||
if (H.url.substr(0, Controller::prometheus.size()+6) == "/"+Controller::prometheus+".json"){
|
||||
if (H.url.substr(0, Controller::prometheus.size() + 6) == "/" + Controller::prometheus + ".json"){
|
||||
handlePrometheus(H, conn, PROMETHEUS_JSON);
|
||||
H.Clean();
|
||||
continue;
|
||||
|
@ -384,26 +382,24 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
}
|
||||
JSON::Value Response;
|
||||
JSON::Value Request = JSON::fromString(H.GetVar("command"));
|
||||
//invalid request? send the web interface, unless requested as "/api"
|
||||
if ( !Request.isObject() && H.url != "/api" && H.url != "/api2"){
|
||||
#include "server.html.h"
|
||||
// invalid request? send the web interface, unless requested as "/api"
|
||||
if (!Request.isObject() && H.url != "/api" && H.url != "/api2"){
|
||||
#include "server.html.h"
|
||||
H.Clean();
|
||||
H.SetHeader("Content-Type", "text/html");
|
||||
H.SetHeader("X-Info", "To force an API response, request the file /api");
|
||||
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
|
||||
H.SetHeader("Content-Length", server_html_len);
|
||||
H.SetHeader("X-UA-Compatible","IE=edge;chrome=1");
|
||||
H.SetHeader("X-UA-Compatible", "IE=edge;chrome=1");
|
||||
H.SendResponse("200", "OK", conn);
|
||||
conn.SendNow(server_html, server_html_len);
|
||||
H.Clean();
|
||||
break;
|
||||
}
|
||||
if (H.url == "/api2"){
|
||||
Request["minimal"] = true;
|
||||
}
|
||||
{//lock the config mutex here - do not unlock until done processing
|
||||
if (H.url == "/api2"){Request["minimal"] = true;}
|
||||
{// lock the config mutex here - do not unlock until done processing
|
||||
tthread::lock_guard<tthread::mutex> guard(configMutex);
|
||||
//if already authorized, do not re-check for authorization
|
||||
// if already authorized, do not re-check for authorization
|
||||
if (authorized && Storage["account"]){
|
||||
Response["authorize"]["status"] = "OK";
|
||||
if (isLocal){Response["authorize"]["local"] = true;}
|
||||
|
@ -412,20 +408,16 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
}
|
||||
if (authorized){
|
||||
handleAPICommands(Request, Response);
|
||||
}else{//unauthorized
|
||||
Util::sleep(1000);//sleep a second to prevent bruteforcing
|
||||
}else{// unauthorized
|
||||
Util::sleep(1000); // sleep a second to prevent bruteforcing
|
||||
logins++;
|
||||
}
|
||||
Controller::checkServerLimits(); /*LTS*/
|
||||
}//config mutex lock
|
||||
//send the response, either normally or through JSONP callback.
|
||||
}// config mutex lock
|
||||
// send the response, either normally or through JSONP callback.
|
||||
std::string jsonp = "";
|
||||
if (H.GetVar("callback") != ""){
|
||||
jsonp = H.GetVar("callback");
|
||||
}
|
||||
if (H.GetVar("jsonp") != ""){
|
||||
jsonp = H.GetVar("jsonp");
|
||||
}
|
||||
if (H.GetVar("callback") != ""){jsonp = H.GetVar("callback");}
|
||||
if (H.GetVar("jsonp") != ""){jsonp = H.GetVar("jsonp");}
|
||||
H.Clean();
|
||||
H.SetHeader("Content-Type", "text/javascript");
|
||||
H.setCORSHeaders();
|
||||
|
@ -436,12 +428,12 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
}
|
||||
H.SendResponse("200", "OK", conn);
|
||||
H.Clean();
|
||||
}//if HTTP request received
|
||||
}//while connected
|
||||
}// if HTTP request received
|
||||
}// while connected
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Controller::handleUDPAPI(void * np){
|
||||
void Controller::handleUDPAPI(void *np){
|
||||
Socket::UDPConnection uSock(true);
|
||||
if (!uSock.bind(UDP_API_PORT, UDP_API_HOST)){
|
||||
FAIL_MSG("Could not open local API UDP socket - not all functionality will be available");
|
||||
|
@ -472,10 +464,8 @@ void Controller::handleUDPAPI(void * np){
|
|||
|
||||
/// Local-only helper function that checks for duplicate protocols and removes them
|
||||
static void removeDuplicateProtocols(){
|
||||
JSON::Value & P = Controller::Storage["config"]["protocols"];
|
||||
jsonForEach(P, it){
|
||||
it->removeNullMembers();
|
||||
}
|
||||
JSON::Value &P = Controller::Storage["config"]["protocols"];
|
||||
jsonForEach(P, it){it->removeNullMembers();}
|
||||
std::set<std::string> ignores;
|
||||
ignores.insert("online");
|
||||
bool reloop = true;
|
||||
|
@ -495,13 +485,13 @@ static void removeDuplicateProtocols(){
|
|||
}
|
||||
}
|
||||
|
||||
void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response){
|
||||
void Controller::handleAPICommands(JSON::Value &Request, JSON::Value &Response){
|
||||
/*LTS-START*/
|
||||
//These are only used internally. We abort further processing if encountered.
|
||||
// These are only used internally. We abort further processing if encountered.
|
||||
if (Request.isMember("trigger_stat")){
|
||||
JSON::Value & tStat = Request["trigger_stat"];
|
||||
JSON::Value &tStat = Request["trigger_stat"];
|
||||
if (tStat.isMember("name") && tStat.isMember("ms")){
|
||||
Controller::triggerLog & tLog = Controller::triggerStats[tStat["name"].asStringRef()];
|
||||
Controller::triggerLog &tLog = Controller::triggerStats[tStat["name"].asStringRef()];
|
||||
tLog.totalCount++;
|
||||
tLog.ms += tStat["ms"].asInt();
|
||||
if (!tStat.isMember("ok") || !tStat["ok"].asBool()){tLog.failCount++;}
|
||||
|
@ -513,14 +503,14 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
return;
|
||||
}
|
||||
/*LTS-END*/
|
||||
//Parse config and streams from the request.
|
||||
// Parse config and streams from the request.
|
||||
if (Request.isMember("config") && Request["config"].isObject()){
|
||||
const JSON::Value & in = Request["config"];
|
||||
JSON::Value & out = Controller::Storage["config"];
|
||||
const JSON::Value &in = Request["config"];
|
||||
JSON::Value &out = Controller::Storage["config"];
|
||||
if (in.isMember("debug")){
|
||||
out["debug"] = in["debug"];
|
||||
if (Util::Config::printDebugLevel != (out["debug"].isInt()?out["debug"].asInt():DEBUG)){
|
||||
Util::Config::printDebugLevel = (out["debug"].isInt()?out["debug"].asInt():DEBUG);
|
||||
if (Util::Config::printDebugLevel != (out["debug"].isInt() ? out["debug"].asInt() : DEBUG)){
|
||||
Util::Config::printDebugLevel = (out["debug"].isInt() ? out["debug"].asInt() : DEBUG);
|
||||
INFO_MSG("Debug level set to %u", Util::Config::printDebugLevel);
|
||||
}
|
||||
}
|
||||
|
@ -532,12 +522,8 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
out["trustedproxy"] = in["trustedproxy"];
|
||||
Controller::normalizeTrustedProxies(out["trustedproxy"]);
|
||||
}
|
||||
if (in.isMember("controller")){
|
||||
out["controller"] = in["controller"];
|
||||
}
|
||||
if (in.isMember("serverid")){
|
||||
out["serverid"] = in["serverid"];
|
||||
}
|
||||
if (in.isMember("controller")){out["controller"] = in["controller"];}
|
||||
if (in.isMember("serverid")){out["serverid"] = in["serverid"];}
|
||||
if (in.isMember("triggers")){
|
||||
out["triggers"] = in["triggers"];
|
||||
if (!out["triggers"].isObject()){
|
||||
|
@ -545,9 +531,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
}else{
|
||||
jsonForEach(out["triggers"], it){
|
||||
if (it->isArray()){
|
||||
jsonForEach((*it), jt){
|
||||
jt->removeNullMembers();
|
||||
}
|
||||
jsonForEach((*it), jt){jt->removeNullMembers();}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -560,9 +544,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
out["prometheus"] = in["prometheus"];
|
||||
Controller::prometheus = out["prometheus"].asStringRef();
|
||||
}
|
||||
if (in.isMember("defaultStream")){
|
||||
out["defaultStream"] = in["defaultStream"];
|
||||
}
|
||||
if (in.isMember("defaultStream")){out["defaultStream"] = in["defaultStream"];}
|
||||
}
|
||||
if (Request.isMember("bandwidth")){
|
||||
if (Request["bandwidth"].isObject()){
|
||||
|
@ -583,94 +565,72 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
Controller::AddStreams(Request["addstream"], Controller::Storage["streams"]);
|
||||
}
|
||||
if (Request.isMember("deletestream")){
|
||||
//if array, delete all elements
|
||||
//if object, delete all entries
|
||||
//if string, delete just the one
|
||||
// if array, delete all elements
|
||||
// if object, delete all entries
|
||||
// if string, delete just the one
|
||||
if (Request["deletestream"].isString()){
|
||||
Controller::deleteStream(Request["deletestream"].asStringRef(), Controller::Storage["streams"]);
|
||||
Controller::deleteStream(Request["deletestream"].asStringRef(),
|
||||
Controller::Storage["streams"]);
|
||||
}
|
||||
if (Request["deletestream"].isArray()){
|
||||
jsonForEach(Request["deletestream"], it){
|
||||
Controller::deleteStream(it->asStringRef(), Controller::Storage["streams"]);
|
||||
Controller::deleteStream(it->asStringRef(), Controller::Storage["streams"]);
|
||||
}
|
||||
}
|
||||
if (Request["deletestream"].isObject()){
|
||||
jsonForEach(Request["deletestream"], it){
|
||||
Controller::deleteStream(it.key(), Controller::Storage["streams"]);
|
||||
Controller::deleteStream(it.key(), Controller::Storage["streams"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Request.isMember("deletestreamsource")){
|
||||
//if array, delete all elements
|
||||
//if object, delete all entries
|
||||
//if string, delete just the one
|
||||
// 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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -699,18 +659,14 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (add){
|
||||
newProtocols.append(*it);
|
||||
}
|
||||
if (add){newProtocols.append(*it);}
|
||||
}
|
||||
Controller::Storage["config"]["protocols"] = newProtocols;
|
||||
}
|
||||
if (Request["deleteprotocol"].isObject()){
|
||||
JSON::Value newProtocols;
|
||||
jsonForEach(Controller::Storage["config"]["protocols"], it){
|
||||
if (!(*it).compareExcept(Request["deleteprotocol"], ignores)){
|
||||
newProtocols.append(*it);
|
||||
}
|
||||
if (!(*it).compareExcept(Request["deleteprotocol"], ignores)){newProtocols.append(*it);}
|
||||
}
|
||||
Controller::Storage["config"]["protocols"] = newProtocols;
|
||||
}
|
||||
|
@ -721,7 +677,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
if (Request["updateprotocol"].isArray() && Request["updateprotocol"].size() == 2){
|
||||
jsonForEach(Controller::Storage["config"]["protocols"], it){
|
||||
if ((*it).compareExcept(Request["updateprotocol"][0u], ignores)){
|
||||
//If the connector type didn't change, mark it as needing a reload
|
||||
// If the connector type didn't change, mark it as needing a reload
|
||||
if ((*it)["connector"] == Request["updateprotocol"][1u]["connector"]){
|
||||
reloadProtocol(it.num());
|
||||
}
|
||||
|
@ -739,32 +695,29 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
Response["capabilities"] = capabilities;
|
||||
}
|
||||
|
||||
|
||||
if(Request.isMember("browse")){
|
||||
if(Request["browse"] == ""){
|
||||
Request["browse"] = ".";
|
||||
}
|
||||
if (Request.isMember("browse")){
|
||||
if (Request["browse"] == ""){Request["browse"] = ".";}
|
||||
DIR *dir;
|
||||
struct dirent *ent;
|
||||
struct stat filestat;
|
||||
char* rpath = realpath(Request["browse"].asString().c_str(),0);
|
||||
if(rpath == NULL){
|
||||
char *rpath = realpath(Request["browse"].asString().c_str(), 0);
|
||||
if (rpath == NULL){
|
||||
Response["browse"]["path"].append(Request["browse"].asString());
|
||||
}else{
|
||||
Response["browse"]["path"].append(rpath);//Request["browse"].asString());
|
||||
if ((dir = opendir (Request["browse"].asString().c_str())) != NULL) {
|
||||
while ((ent = readdir (dir)) != NULL) {
|
||||
if(strcmp(ent->d_name,".")!=0 && strcmp(ent->d_name,"..")!=0 ){
|
||||
Response["browse"]["path"].append(rpath); // Request["browse"].asString());
|
||||
if ((dir = opendir(Request["browse"].asString().c_str())) != NULL){
|
||||
while ((ent = readdir(dir)) != NULL){
|
||||
if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0){
|
||||
std::string filepath = Request["browse"].asString() + "/" + std::string(ent->d_name);
|
||||
if (stat( filepath.c_str(), &filestat )) continue;
|
||||
if (S_ISDIR( filestat.st_mode)){
|
||||
if (stat(filepath.c_str(), &filestat)) continue;
|
||||
if (S_ISDIR(filestat.st_mode)){
|
||||
Response["browse"]["subdirectories"].append(ent->d_name);
|
||||
}else{
|
||||
Response["browse"]["files"].append(ent->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir (dir);
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
free(rpath);
|
||||
|
@ -781,18 +734,24 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
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());
|
||||
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());
|
||||
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());
|
||||
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){
|
||||
|
@ -804,24 +763,24 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
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());
|
||||
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 {
|
||||
do{
|
||||
std::getline(plsRead, line);
|
||||
if (line.size() || plsRead.good()){lines.push_back(line);}
|
||||
} while(plsRead.good());
|
||||
}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()];
|
||||
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());
|
||||
|
@ -832,9 +791,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
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 (plsNo < lines.size() || (*plsIt).size()){plsOutFile << (*plsIt) << "\n";}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -848,32 +805,30 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
}
|
||||
|
||||
if (Request.isMember("ui_settings")){
|
||||
if (Request["ui_settings"].isObject()){
|
||||
Storage["ui_settings"] = Request["ui_settings"];
|
||||
}
|
||||
if (Request["ui_settings"].isObject()){Storage["ui_settings"] = Request["ui_settings"];}
|
||||
Response["ui_settings"] = Storage["ui_settings"];
|
||||
}
|
||||
/*LTS-START*/
|
||||
///
|
||||
///
|
||||
/// \api
|
||||
/// LTS builds will always include an `"LTS"` response, set to 1.
|
||||
///
|
||||
///
|
||||
Response["LTS"] = 1;
|
||||
///
|
||||
/// \api
|
||||
/// `"autoupdate"` requests (LTS-only) will cause MistServer to apply a rolling update to itself, and are not responded to.
|
||||
///
|
||||
#ifdef UPDATER
|
||||
if (Request.isMember("autoupdate")){
|
||||
Controller::checkUpdates();
|
||||
}
|
||||
///
|
||||
/// \api
|
||||
/// `"autoupdate"` requests (LTS-only) will cause MistServer to apply a rolling update to itself, and are not responded to.
|
||||
///
|
||||
#ifdef UPDATER
|
||||
if (Request.isMember("autoupdate")){Controller::checkUpdates();}
|
||||
if (Request.isMember("update") || Request.isMember("checkupdate") || Request.isMember("autoupdate")){
|
||||
Controller::insertUpdateInfo(Response["update"]);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
if (!Request.isMember("minimal") || Request.isMember("streams") || Request.isMember("addstream") || Request.isMember("deletestream")){
|
||||
if (!Request.isMember("streams") && (Request.isMember("addstream") || Request.isMember("deletestream"))){
|
||||
if (!Request.isMember("minimal") || Request.isMember("streams") ||
|
||||
Request.isMember("addstream") || Request.isMember("deletestream")){
|
||||
if (!Request.isMember("streams") &&
|
||||
(Request.isMember("addstream") || Request.isMember("deletestream"))){
|
||||
Response["streams"]["incomplete list"] = 1u;
|
||||
if (Request.isMember("addstream")){
|
||||
jsonForEach(Request["addstream"], jit){
|
||||
|
@ -886,24 +841,22 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
Response["streams"] = Controller::Storage["streams"];
|
||||
}
|
||||
}
|
||||
//sent current configuration, if not minimal or was changed/requested
|
||||
// sent current configuration, if not minimal or was changed/requested
|
||||
if (!Request.isMember("minimal") || Request.isMember("config")){
|
||||
Response["config"] = Controller::Storage["config"];
|
||||
Response["config"]["iid"] = instanceId;
|
||||
Response["config"]["version"] = PACKAGE_VERSION " " RELEASE;
|
||||
/*LTS-START*/
|
||||
#ifdef LICENSING
|
||||
/*LTS-START*/
|
||||
#ifdef LICENSING
|
||||
Response["config"]["license"] = getLicense();
|
||||
#endif
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
//add required data to the current unix time to the config, for syncing reasons
|
||||
// add required data to the current unix time to the config, for syncing reasons
|
||||
Response["config"]["time"] = Util::epoch();
|
||||
if ( !Response["config"].isMember("serverid")){
|
||||
Response["config"]["serverid"] = "";
|
||||
}
|
||||
if (!Response["config"].isMember("serverid")){Response["config"]["serverid"] = "";}
|
||||
}
|
||||
//sent any available logs and statistics
|
||||
///
|
||||
// sent any available logs and statistics
|
||||
///
|
||||
/// \api
|
||||
/// `"log"` responses are always sent, and cannot be requested:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
|
@ -911,22 +864,20 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
/// [
|
||||
/// 1398978357, //unix timestamp of this log message
|
||||
/// "CONF", //shortcode indicating the type of log message
|
||||
/// "Starting connector: {\"connector\":\"HTTP\"}" //string containing the log message itself
|
||||
/// "Starting connector:{\"connector\":\"HTTP\"}" //string containing the log message itself
|
||||
/// ],
|
||||
/// //the above structure repeated for all logs
|
||||
/// ]
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// It's possible to clear the stored logs by sending an empty `"clearstatlogs"` request.
|
||||
///
|
||||
///
|
||||
if (Request.isMember("clearstatlogs") || Request.isMember("log") || !Request.isMember("minimal")){
|
||||
tthread::lock_guard<tthread::mutex> guard(logMutex);
|
||||
if (!Request.isMember("minimal") || Request.isMember("log")){
|
||||
Response["log"] = Controller::Storage["log"];
|
||||
}
|
||||
//clear log if requested
|
||||
if (Request.isMember("clearstatlogs")){
|
||||
Controller::Storage["log"].null();
|
||||
}
|
||||
// clear log if requested
|
||||
if (Request.isMember("clearstatlogs")){Controller::Storage["log"].null();}
|
||||
}
|
||||
if (Request.isMember("clients")){
|
||||
if (Request["clients"].isArray()){
|
||||
|
@ -991,9 +942,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
|
||||
if (Request.isMember("stop_sessions")){
|
||||
if (Request["stop_sessions"].isArray() || Request["stop_sessions"].isObject()){
|
||||
jsonForEach(Request["stop_sessions"], it){
|
||||
Controller::sessions_shutdown(it);
|
||||
}
|
||||
jsonForEach(Request["stop_sessions"], it){Controller::sessions_shutdown(it);}
|
||||
}else{
|
||||
Controller::sessions_shutdown(Request["stop_sessions"].asStringRef());
|
||||
}
|
||||
|
@ -1001,9 +950,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
|
||||
if (Request.isMember("stop_sessid")){
|
||||
if (Request["stop_sessid"].isArray() || Request["stop_sessid"].isObject()){
|
||||
jsonForEach(Request["stop_sessid"], it){
|
||||
Controller::sessId_shutdown(it->asStringRef());
|
||||
}
|
||||
jsonForEach(Request["stop_sessid"], it){Controller::sessId_shutdown(it->asStringRef());}
|
||||
}else{
|
||||
Controller::sessId_shutdown(Request["stop_sessid"].asStringRef());
|
||||
}
|
||||
|
@ -1011,9 +958,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
|
||||
if (Request.isMember("stop_tag")){
|
||||
if (Request["stop_tag"].isArray() || Request["stop_tag"].isObject()){
|
||||
jsonForEach(Request["stop_tag"], it){
|
||||
Controller::tag_shutdown(it->asStringRef());
|
||||
}
|
||||
jsonForEach(Request["stop_tag"], it){Controller::tag_shutdown(it->asStringRef());}
|
||||
}else{
|
||||
Controller::tag_shutdown(Request["stop_tag"].asStringRef());
|
||||
}
|
||||
|
@ -1027,7 +972,6 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if (Request.isMember("push_start")){
|
||||
std::string stream;
|
||||
std::string target;
|
||||
|
@ -1053,29 +997,21 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
}
|
||||
}
|
||||
|
||||
if (Request.isMember("push_list")){
|
||||
Controller::listPush(Response["push_list"]);
|
||||
}
|
||||
|
||||
if (Request.isMember("push_list")){Controller::listPush(Response["push_list"]);}
|
||||
|
||||
if (Request.isMember("push_stop")){
|
||||
if (Request["push_stop"].isArray()){
|
||||
jsonForEach(Request["push_stop"], it){
|
||||
Controller::stopPush(it->asInt());
|
||||
}
|
||||
jsonForEach(Request["push_stop"], it){Controller::stopPush(it->asInt());}
|
||||
}else{
|
||||
Controller::stopPush(Request["push_stop"].asInt());
|
||||
}
|
||||
}
|
||||
|
||||
if (Request.isMember("push_auto_add")){
|
||||
Controller::addPush(Request["push_auto_add"]);
|
||||
}
|
||||
if (Request.isMember("push_auto_add")){Controller::addPush(Request["push_auto_add"]);}
|
||||
|
||||
if (Request.isMember("push_auto_remove")){
|
||||
if (Request["push_auto_remove"].isArray()){
|
||||
jsonForEach(Request["push_auto_remove"], it){
|
||||
Controller::removePush(*it);
|
||||
}
|
||||
jsonForEach(Request["push_auto_remove"], it){Controller::removePush(*it);}
|
||||
}else{
|
||||
Controller::removePush(Request["push_auto_remove"]);
|
||||
}
|
||||
|
@ -1089,8 +1025,6 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
|||
Controller::pushSettings(Request["push_settings"], Response["push_settings"]);
|
||||
}
|
||||
|
||||
|
||||
Controller::writeConfig();
|
||||
Controller::configChanged = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#include <mist/socket.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/websocket.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/websocket.h>
|
||||
|
||||
namespace Controller {
|
||||
bool authorize(JSON::Value & Request, JSON::Value & Response, Socket::Connection & conn);
|
||||
int handleAPIConnection(Socket::Connection & conn);
|
||||
void handleAPICommands(JSON::Value & Request, JSON::Value & Response);
|
||||
void handleWebSocket(HTTP::Parser & H, Socket::Connection & C);
|
||||
void handleUDPAPI(void * np);
|
||||
}
|
||||
namespace Controller{
|
||||
bool authorize(JSON::Value &Request, JSON::Value &Response, Socket::Connection &conn);
|
||||
int handleAPIConnection(Socket::Connection &conn);
|
||||
void handleAPICommands(JSON::Value &Request, JSON::Value &Response);
|
||||
void handleWebSocket(HTTP::Parser &H, Socket::Connection &C);
|
||||
void handleUDPAPI(void *np);
|
||||
}// namespace Controller
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
#include "controller_capabilities.h"
|
||||
#include <fstream>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/procs.h>
|
||||
#include <set>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/procs.h>
|
||||
#include "controller_capabilities.h"
|
||||
|
||||
///\brief Holds everything unique to the controller.
|
||||
namespace Controller {
|
||||
|
||||
namespace Controller{
|
||||
|
||||
JSON::Value capabilities;
|
||||
//Converter::Converter * myConverter = 0;
|
||||
|
||||
///Generate list of available triggers, storing in global 'capabilities' JSON::Value.
|
||||
// Converter::Converter * myConverter = 0;
|
||||
|
||||
/// Generate list of available triggers, storing in global 'capabilities' JSON::Value.
|
||||
void checkAvailTriggers(){
|
||||
JSON::Value & trgs = capabilities["triggers"];
|
||||
JSON::Value &trgs = capabilities["triggers"];
|
||||
trgs["SYSTEM_START"]["when"] = "After MistServer boot";
|
||||
trgs["SYSTEM_START"]["stream_specific"] = false;
|
||||
trgs["SYSTEM_START"]["payload"] = "";
|
||||
|
@ -50,19 +50,24 @@ namespace Controller {
|
|||
trgs["STREAM_CONFIG"]["stream_specific"] = true;
|
||||
trgs["STREAM_CONFIG"]["payload"] = "stream name (string)\nnew stream configuration (JSON)";
|
||||
trgs["STREAM_CONFIG"]["response"] = "always";
|
||||
trgs["STREAM_CONFIG"]["response_action"] = "If false, rejects new configuration and reverts to current configuration.";
|
||||
trgs["STREAM_CONFIG"]["response_action"] =
|
||||
"If false, rejects new configuration and reverts to current configuration.";
|
||||
|
||||
trgs["STREAM_REMOVE"]["when"] = "Before an existing stream is removed";
|
||||
trgs["STREAM_REMOVE"]["stream_specific"] = true;
|
||||
trgs["STREAM_REMOVE"]["payload"] = "stream name (string)";
|
||||
trgs["STREAM_REMOVE"]["response"] = "always";
|
||||
trgs["STREAM_REMOVE"]["response_action"] = "If false, prevents removal and reverts to current configuration.";
|
||||
trgs["STREAM_REMOVE"]["response_action"] =
|
||||
"If false, prevents removal and reverts to current configuration.";
|
||||
|
||||
trgs["STREAM_SOURCE"]["when"] = "When a stream's source setting is loaded";
|
||||
trgs["STREAM_SOURCE"]["stream_specific"] = true;
|
||||
trgs["STREAM_SOURCE"]["payload"] = "stream name (string)";
|
||||
trgs["STREAM_SOURCE"]["response"] = "when-blocking";
|
||||
trgs["STREAM_SOURCE"]["response_action"] = "A non-empty response will set the stream source to the response value. An empty response will cause the stream source to not be changed from the normally configured stream source.";
|
||||
trgs["STREAM_SOURCE"]["response_action"] =
|
||||
"A non-empty response will set the stream source to the response value. An empty response "
|
||||
"will cause the stream source to not be changed from the normally configured stream "
|
||||
"source.";
|
||||
|
||||
trgs["STREAM_LOAD"]["when"] = "Before a stream input is loaded";
|
||||
trgs["STREAM_LOAD"]["stream_specific"] = true;
|
||||
|
@ -80,11 +85,13 @@ namespace Controller {
|
|||
trgs["STREAM_UNLOAD"]["stream_specific"] = true;
|
||||
trgs["STREAM_UNLOAD"]["payload"] = "stream name (string)\ninput type (string)";
|
||||
trgs["STREAM_UNLOAD"]["response"] = "always";
|
||||
trgs["STREAM_UNLOAD"]["response_action"] = "If false, aborts the unload and keeps the stream loaded.";
|
||||
trgs["STREAM_UNLOAD"]["response_action"] =
|
||||
"If false, aborts the unload and keeps the stream loaded.";
|
||||
|
||||
trgs["STREAM_PUSH"]["when"] = "Before an incoming push is accepted";
|
||||
trgs["STREAM_PUSH"]["stream_specific"] = true;
|
||||
trgs["STREAM_PUSH"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
|
||||
trgs["STREAM_PUSH"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
|
||||
"(string)\nrequest url (string)";
|
||||
trgs["STREAM_PUSH"]["response"] = "always";
|
||||
trgs["STREAM_PUSH"]["response_action"] = "If false, rejects the incoming push.";
|
||||
|
||||
|
@ -102,55 +109,79 @@ namespace Controller {
|
|||
|
||||
trgs["STREAM_BUFFER"]["when"] = "Every time a live stream buffer changes state";
|
||||
trgs["STREAM_BUFFER"]["stream_specific"] = true;
|
||||
trgs["STREAM_BUFFER"]["payload"] = "stream name (string)\nbuffer state: EMPTY, FULL, DRY or RECOVER (string)\nbuffer health information (only if not EMPTY) (JSON)";
|
||||
trgs["STREAM_BUFFER"]["payload"] =
|
||||
"stream name (string)\nbuffer state: EMPTY, FULL, DRY or RECOVER (string)\nbuffer health "
|
||||
"information (only if not EMPTY) (JSON)";
|
||||
trgs["STREAM_BUFFER"]["response"] = "ignored";
|
||||
trgs["STREAM_BUFFER"]["response_action"] = "None.";
|
||||
|
||||
trgs["RTMP_PUSH_REWRITE"]["when"] = "On incoming RTMP pushes, allows rewriting the RTMP URL to/from custom formatting";
|
||||
trgs["RTMP_PUSH_REWRITE"]["when"] =
|
||||
"On incoming RTMP pushes, allows rewriting the RTMP URL to/from custom formatting";
|
||||
trgs["RTMP_PUSH_REWRITE"]["stream_specific"] = false;
|
||||
trgs["RTMP_PUSH_REWRITE"]["payload"] = "full current RTMP url (string)\nconnection hostname (string)";
|
||||
trgs["RTMP_PUSH_REWRITE"]["payload"] =
|
||||
"full current RTMP url (string)\nconnection hostname (string)";
|
||||
trgs["RTMP_PUSH_REWRITE"]["response"] = "when-blocking";
|
||||
trgs["RTMP_PUSH_REWRITE"]["response_action"] = "If non-empty, overrides the full RTMP url to the response value. If empty, denies the incoming RTMP push.";
|
||||
trgs["RTMP_PUSH_REWRITE"]["response_action"] =
|
||||
"If non-empty, overrides the full RTMP url to the response value. If empty, denies the "
|
||||
"incoming RTMP push.";
|
||||
|
||||
trgs["PUSH_OUT_START"]["when"] = "Before a push out (to file or other target type) is started";
|
||||
trgs["PUSH_OUT_START"]["stream_specific"] = true;
|
||||
trgs["PUSH_OUT_START"]["payload"] = "stream name (string)\npush target (string)";
|
||||
trgs["PUSH_OUT_START"]["response"] = "when-blocking";
|
||||
trgs["PUSH_OUT_START"]["response_action"] = "A non-empty response will set the push target to the response value. An empty response will abort the push. Variable substitution will still take place.";
|
||||
trgs["PUSH_OUT_START"]["response_action"] =
|
||||
"A non-empty response will set the push target to the response value. An empty response "
|
||||
"will abort the push. Variable substitution will still take place.";
|
||||
|
||||
trgs["RECORDING_END"]["when"] = "When a push to file finishes";
|
||||
trgs["RECORDING_END"]["stream_specific"] = true;
|
||||
trgs["RECORDING_END"]["payload"] = "stream name (string)\npush target (string)\nconnector / filetype (string)\nbytes recorded (integer)\nseconds spent recording (integer)\nunix time recording started (integer)\nunix time recording stopped (integer)\ntotal milliseconds of media data recorded (integer)\nmillisecond timestamp of first media packet (integer)\nmillisecond timestamp of last media packet (integer)\n";
|
||||
trgs["RECORDING_END"]["payload"] =
|
||||
"stream name (string)\npush target (string)\nconnector / filetype (string)\nbytes recorded "
|
||||
"(integer)\nseconds spent recording (integer)\nunix time recording started (integer)\nunix "
|
||||
"time recording stopped (integer)\ntotal milliseconds of media data recorded "
|
||||
"(integer)\nmillisecond timestamp of first media packet (integer)\nmillisecond timestamp "
|
||||
"of last media packet (integer)\n";
|
||||
trgs["RECORDING_END"]["response"] = "ignored";
|
||||
trgs["RECORDING_END"]["response_action"] = "None.";
|
||||
|
||||
trgs["CONN_OPEN"]["when"] = "After a new connection is accepted";
|
||||
trgs["CONN_OPEN"]["stream_specific"] = true;
|
||||
trgs["CONN_OPEN"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
|
||||
trgs["CONN_OPEN"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
|
||||
"(string)\nrequest url (string)";
|
||||
trgs["CONN_OPEN"]["response"] = "always";
|
||||
trgs["CONN_OPEN"]["response_action"] = "If false, rejects the connection.";
|
||||
|
||||
trgs["CONN_CLOSE"]["when"] = "After a new connection is closed";
|
||||
trgs["CONN_CLOSE"]["stream_specific"] = true;
|
||||
trgs["CONN_CLOSE"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
|
||||
trgs["CONN_CLOSE"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
|
||||
"(string)\nrequest url (string)";
|
||||
trgs["CONN_CLOSE"]["response"] = "ignored";
|
||||
trgs["CONN_CLOSE"]["response_action"] = "None.";
|
||||
|
||||
trgs["CONN_PLAY"]["when"] = "Before a connection first starts playback";
|
||||
trgs["CONN_PLAY"]["stream_specific"] = true;
|
||||
trgs["CONN_PLAY"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
|
||||
trgs["CONN_PLAY"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
|
||||
"(string)\nrequest url (string)";
|
||||
trgs["CONN_PLAY"]["response"] = "always";
|
||||
trgs["CONN_PLAY"]["response_action"] = "If false, rejects the playback attempt.";
|
||||
|
||||
trgs["USER_NEW"]["when"] = "Every time a new session is added to the session cache";
|
||||
trgs["USER_NEW"]["stream_specific"] = true;
|
||||
trgs["USER_NEW"]["payload"] = "stream name (string)\nconnection address (string)\nconnection identifier (integer)\nconnector (string)\nrequest url (string)\nsession identifier (integer)";
|
||||
trgs["USER_NEW"]["payload"] =
|
||||
"stream name (string)\nconnection address (string)\nconnection identifier "
|
||||
"(integer)\nconnector (string)\nrequest url (string)\nsession identifier (integer)";
|
||||
trgs["USER_NEW"]["response"] = "always";
|
||||
trgs["USER_NEW"]["response_action"] = "If false, denies the session while it remains in the cache. If true, accepts the session while it remains in the cache.";
|
||||
trgs["USER_NEW"]["response_action"] =
|
||||
"If false, denies the session while it remains in the cache. If true, accepts the session "
|
||||
"while it remains in the cache.";
|
||||
|
||||
trgs["USER_END"]["when"] = "Every time a session ends (same time it is written to the access log)";
|
||||
trgs["USER_END"]["when"] =
|
||||
"Every time a session ends (same time it is written to the access log)";
|
||||
trgs["USER_END"]["stream_specific"] = true;
|
||||
trgs["USER_END"]["payload"] = "session identifier (hexadecimal string)\nstream name (string)\nconnector (string)\nconnection address (string)\nduration in seconds (integer)\nuploaded bytes total (integer)\ndownloaded bytes total (integer)\ntags (string)";
|
||||
trgs["USER_END"]["payload"] =
|
||||
"session identifier (hexadecimal string)\nstream name (string)\nconnector "
|
||||
"(string)\nconnection address (string)\nduration in seconds (integer)\nuploaded bytes "
|
||||
"total (integer)\ndownloaded bytes total (integer)\ntags (string)";
|
||||
trgs["USER_END"]["response"] = "ignored";
|
||||
trgs["USER_END"]["response_action"] = "None.";
|
||||
|
||||
|
@ -159,31 +190,38 @@ namespace Controller {
|
|||
trgs["LIVE_BANDWIDTH"]["payload"] = "stream name (string)\ncurrent bytes per second (integer)";
|
||||
trgs["LIVE_BANDWIDTH"]["response"] = "always";
|
||||
trgs["LIVE_BANDWIDTH"]["response_action"] = "If false, shuts down the stream buffer.";
|
||||
trgs["LIVE_BANDWIDTH"]["argument"] = "Triggers only if current bytes per second exceeds this amount (integer)";
|
||||
trgs["LIVE_BANDWIDTH"]["argument"] =
|
||||
"Triggers only if current bytes per second exceeds this amount (integer)";
|
||||
|
||||
trgs["DEFAULT_STREAM"]["when"] = "When any user attempts to open a stream that cannot be opened (because it is either offline or not configured), allows rewriting the stream to a different one as fallback. Supports variable substitution.";
|
||||
trgs["DEFAULT_STREAM"]["when"] =
|
||||
"When any user attempts to open a stream that cannot be opened (because it is either "
|
||||
"offline or not configured), allows rewriting the stream to a different one as fallback. "
|
||||
"Supports variable substitution.";
|
||||
trgs["DEFAULT_STREAM"]["stream_specific"] = true;
|
||||
trgs["DEFAULT_STREAM"]["payload"] = "current defaultStream setting (string)\nrequested stream name (string)\nviewer host (string)\noutput type (string)\nfull request URL (string, may be blank for non-URL-based requests!)";
|
||||
trgs["DEFAULT_STREAM"]["payload"] =
|
||||
"current defaultStream setting (string)\nrequested stream name (string)\nviewer host "
|
||||
"(string)\noutput type (string)\nfull request URL (string, may be blank for non-URL-based "
|
||||
"requests!)";
|
||||
trgs["DEFAULT_STREAM"]["response"] = "always";
|
||||
trgs["DEFAULT_STREAM"]["response_action"] = "Overrides the default stream setting (for this view) to the response value. If empty, fails loading the stream and returns an error to the viewer/user.";
|
||||
|
||||
trgs["DEFAULT_STREAM"]["response_action"] =
|
||||
"Overrides the default stream setting (for this view) to the response value. If empty, "
|
||||
"fails loading the stream and returns an error to the viewer/user.";
|
||||
}
|
||||
|
||||
///Aquire list of available protocols, storing in global 'capabilities' JSON::Value.
|
||||
|
||||
/// Aquire list of available protocols, storing in global 'capabilities' JSON::Value.
|
||||
void checkAvailProtocols(){
|
||||
std::deque<std::string> execs;
|
||||
Util::getMyExec(execs);
|
||||
std::string arg_one;
|
||||
char const * conn_args[] = {0, "-j", 0};
|
||||
char const *conn_args[] ={0, "-j", 0};
|
||||
for (std::deque<std::string>::iterator it = execs.begin(); it != execs.end(); it++){
|
||||
if ((*it).substr(0, 8) == "MistConn"){
|
||||
//skip if an MistOut already existed - MistOut takes precedence!
|
||||
if (capabilities["connectors"].isMember((*it).substr(8))){
|
||||
continue;
|
||||
}
|
||||
// skip if an MistOut already existed - MistOut takes precedence!
|
||||
if (capabilities["connectors"].isMember((*it).substr(8))){continue;}
|
||||
arg_one = Util::getMyPath() + (*it);
|
||||
conn_args[0] = arg_one.c_str();
|
||||
capabilities["connectors"][(*it).substr(8)] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
|
||||
capabilities["connectors"][(*it).substr(8)] =
|
||||
JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
|
||||
if (capabilities["connectors"][(*it).substr(8)].size() < 1){
|
||||
capabilities["connectors"].removeMember((*it).substr(8));
|
||||
}
|
||||
|
@ -192,18 +230,21 @@ namespace Controller {
|
|||
arg_one = Util::getMyPath() + (*it);
|
||||
conn_args[0] = arg_one.c_str();
|
||||
std::string entryName = (*it).substr(7);
|
||||
capabilities["connectors"][entryName] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
|
||||
capabilities["connectors"][entryName] =
|
||||
JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
|
||||
if (capabilities["connectors"][entryName].size() < 1){
|
||||
capabilities["connectors"].removeMember(entryName);
|
||||
}else if (capabilities["connectors"][entryName]["version"].asStringRef() != PACKAGE_VERSION){
|
||||
WARN_MSG("Output %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(), capabilities["connectors"][entryName]["version"].asStringRef().c_str());
|
||||
WARN_MSG("Output %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(),
|
||||
capabilities["connectors"][entryName]["version"].asStringRef().c_str());
|
||||
capabilities["connectors"].removeMember(entryName);
|
||||
}
|
||||
}
|
||||
if ((*it).substr(0, 8) == "MistProc"){
|
||||
arg_one = Util::getMyPath() + (*it);
|
||||
conn_args[0] = arg_one.c_str();
|
||||
capabilities["processes"][(*it).substr(8)] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
|
||||
capabilities["processes"][(*it).substr(8)] =
|
||||
JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
|
||||
if (capabilities["processes"][(*it).substr(8)].size() < 1){
|
||||
capabilities["processes"].removeMember((*it).substr(7));
|
||||
}
|
||||
|
@ -212,58 +253,47 @@ namespace Controller {
|
|||
arg_one = Util::getMyPath() + (*it);
|
||||
conn_args[0] = arg_one.c_str();
|
||||
std::string entryName = (*it).substr(6);
|
||||
capabilities["inputs"][entryName] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
|
||||
capabilities["inputs"][entryName] = JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
|
||||
if (capabilities["inputs"][entryName].size() < 1){
|
||||
capabilities["inputs"].removeMember((*it).substr(6));
|
||||
}else if (capabilities["inputs"][entryName]["version"].asStringRef() != PACKAGE_VERSION){
|
||||
WARN_MSG("Input %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(), capabilities["inputs"][entryName]["version"].asStringRef().c_str());
|
||||
WARN_MSG("Input %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(),
|
||||
capabilities["inputs"][entryName]["version"].asStringRef().c_str());
|
||||
capabilities["inputs"].removeMember(entryName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///\brief A class storing information about the cpu the server is running on.
|
||||
class cpudata{
|
||||
public:
|
||||
std::string model;///<A string describing the model of the cpu.
|
||||
int cores;///<The amount of cores in the cpu.
|
||||
int threads;///<The amount of threads this cpu can run.
|
||||
int mhz;///<The speed of the cpu in mhz.
|
||||
int id;///<The id of the cpu in the system.
|
||||
|
||||
///\brief The default constructor
|
||||
cpudata(){
|
||||
model = "Unknown";
|
||||
cores = 1;
|
||||
threads = 1;
|
||||
mhz = 0;
|
||||
id = 0;
|
||||
}
|
||||
;
|
||||
|
||||
///\brief Fills the structure by parsing a given description.
|
||||
///\param data A description of the cpu.
|
||||
void fill(char * data){
|
||||
int i;
|
||||
i = 0;
|
||||
if (sscanf(data, "model name : %n", &i) != EOF && i > 0){
|
||||
model = (data + i);
|
||||
}
|
||||
if (sscanf(data, "cpu cores : %d", &i) == 1){
|
||||
cores = i;
|
||||
}
|
||||
if (sscanf(data, "siblings : %d", &i) == 1){
|
||||
threads = i;
|
||||
}
|
||||
if (sscanf(data, "physical id : %d", &i) == 1){
|
||||
id = i;
|
||||
}
|
||||
if (sscanf(data, "cpu MHz : %d", &i) == 1){
|
||||
mhz = i;
|
||||
}
|
||||
}
|
||||
;
|
||||
public:
|
||||
std::string model; ///< A string describing the model of the cpu.
|
||||
int cores; ///< The amount of cores in the cpu.
|
||||
int threads; ///< The amount of threads this cpu can run.
|
||||
int mhz; ///< The speed of the cpu in mhz.
|
||||
int id; ///< The id of the cpu in the system.
|
||||
|
||||
///\brief The default constructor
|
||||
cpudata(){
|
||||
model = "Unknown";
|
||||
cores = 1;
|
||||
threads = 1;
|
||||
mhz = 0;
|
||||
id = 0;
|
||||
};
|
||||
|
||||
///\brief Fills the structure by parsing a given description.
|
||||
///\param data A description of the cpu.
|
||||
void fill(char *data){
|
||||
int i;
|
||||
i = 0;
|
||||
if (sscanf(data, "model name : %n", &i) != EOF && i > 0){model = (data + i);}
|
||||
if (sscanf(data, "cpu cores : %d", &i) == 1){cores = i;}
|
||||
if (sscanf(data, "siblings : %d", &i) == 1){threads = i;}
|
||||
if (sscanf(data, "physical id : %d", &i) == 1){id = i;}
|
||||
if (sscanf(data, "cpu MHz : %d", &i) == 1){mhz = i;}
|
||||
};
|
||||
};
|
||||
|
||||
///\brief Checks the capabilities of the system.
|
||||
|
@ -272,73 +302,78 @@ namespace Controller {
|
|||
/// \api
|
||||
/// `"capabilities"` requests are always empty:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {}
|
||||
///{}
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// and are responded to as:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
/// "connectors": { // a list of installed connectors
|
||||
/// "FLV": { //name of the connector. This is based on the executable filename, with the "MistIn" / "MistConn" prefix stripped.
|
||||
///{
|
||||
/// "connectors":{// a list of installed connectors
|
||||
/// "FLV":{//name of the connector. This is based on the executable filename, with the
|
||||
/// "MistIn" / "MistConn" prefix stripped.
|
||||
/// "codecs": [ //supported combinations of codecs.
|
||||
/// [["H264","H263","VP6"],["AAC","MP3"]] //one such combination, listing simultaneously available channels and the codec options for those channels
|
||||
/// [["H264","H263","VP6"],["AAC","MP3"]] //one such combination, listing simultaneously
|
||||
/// available channels and the codec options for those channels
|
||||
/// ],
|
||||
/// "deps": "HTTP", //dependencies on other connectors, if any.
|
||||
/// "desc": "Enables HTTP protocol progressive streaming.", //human-friendly description of this connector
|
||||
/// "methods": [ //list of supported request methods
|
||||
/// {
|
||||
/// "handler": "http", //what handler to use for this request method. The "http://" part of a URL, without the "://".
|
||||
/// "priority": 5, // priority of this request method, higher is better.
|
||||
/// "type": "flash/7" //type of request method - usually name of plugin followed by the minimal plugin version, or 'HTML5' for pluginless.
|
||||
/// }
|
||||
/// "desc": "Enables HTTP protocol progressive streaming.", //human-friendly description of
|
||||
/// this connector "methods": [ //list of supported request methods
|
||||
///{
|
||||
/// "handler": "http", //what handler to use for this request method. The "http://" part
|
||||
/// of a URL, without the "://". "priority": 5, // priority of this request method,
|
||||
/// higher is better. "type": "flash/7" //type of request method - usually name of
|
||||
/// plugin followed by the minimal plugin version, or 'HTML5' for pluginless.
|
||||
///}
|
||||
/// ],
|
||||
/// "name": "HTTP_Progressive_FLV", //Full name of this connector.
|
||||
/// "optional": { //optional parameters
|
||||
/// "username": { //name of the parameter
|
||||
/// "help": "Username to drop privileges to - default if unprovided means do not drop privileges", //human-readable help text
|
||||
/// "name": "Username", //human-readable name of parameter
|
||||
/// "option": "--username", //command-line option to use
|
||||
/// "type": "str" //type of option - "str" or "num"
|
||||
/// }
|
||||
/// "optional":{//optional parameters
|
||||
/// "username":{//name of the parameter
|
||||
/// "help": "Username to drop privileges to - default if unprovided means do not drop
|
||||
/// privileges", //human-readable help text "name": "Username", //human-readable name of
|
||||
/// parameter "option": "--username", //command-line option to use "type": "str" //type
|
||||
/// of option - "str" or "num"
|
||||
///}
|
||||
/// //above structure repeated for all (optional) parameters
|
||||
/// },
|
||||
///},
|
||||
/// //above structure repeated, as "required" for required parameters, if any.
|
||||
/// "socket": "http_progressive_flv", //unix socket this connector listens on, if any
|
||||
/// "url_match": "/$.flv", //URL pattern to match, if any. The $ substitutes the stream name and may not be the first or last character.
|
||||
/// "url_prefix": "/progressive/$/", //URL prefix to match, if any. The $ substitutes the stream name and may not be the first or last character.
|
||||
/// "url_rel": "/$.flv" //relative URL where to access a stream through this connector.
|
||||
/// }
|
||||
/// "url_match": "/$.flv", //URL pattern to match, if any. The $ substitutes the stream name
|
||||
/// and may not be the first or last character. "url_prefix": "/progressive/$/", //URL
|
||||
/// prefix to match, if any. The $ substitutes the stream name and may not be the first or
|
||||
/// last character. "url_rel": "/$.flv" //relative URL where to access a stream through this
|
||||
/// connector.
|
||||
///}
|
||||
/// //... above structure repeated for all installed connectors.
|
||||
/// },
|
||||
///},
|
||||
/// "cpu": [ //a list of installed CPUs
|
||||
/// {
|
||||
///{
|
||||
/// "cores": 4, //amount of cores for this CPU
|
||||
/// "mhz": 1645, //speed in MHz for this CPU
|
||||
/// "model": "Intel(R) Core(TM) i7-2630QM CPU @ 2.00GHz", //model identifier, for humans
|
||||
/// "threads": 8 //amount of simultaneously executing threads that are supported on this CPU
|
||||
/// }
|
||||
///}
|
||||
/// //above structure repeated for all installed CPUs
|
||||
/// ],
|
||||
/// "load": {
|
||||
/// "load":{
|
||||
/// "fifteen": 72,
|
||||
/// "five": 81,
|
||||
/// "memory": 42,
|
||||
/// "one": 124
|
||||
/// },
|
||||
/// "mem": {
|
||||
///},
|
||||
/// "mem":{
|
||||
/// "cached": 1989, //current memory usage of system caches, in MiB
|
||||
/// "free": 2539, //free memory, in MiB
|
||||
/// "swapfree": 0, //free swap space, in MiB
|
||||
/// "swaptotal": 0, //total swap space, in MiB
|
||||
/// "total": 7898, //total memory, in MiB
|
||||
/// "used": 3370 //used memory, in MiB (excluding system caches, listed separately)
|
||||
/// },
|
||||
///},
|
||||
/// "speed": 6580, //total speed in MHz of all CPUs cores summed together
|
||||
/// "threads": 8 //total count of all threads of all CPUs summed together
|
||||
/// "cpu_use": 105 //Tenths of percent CPU usage - i.e. 105 = 10.5%
|
||||
/// }
|
||||
///}
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
void checkCapable(JSON::Value & capa){
|
||||
//capa.null();
|
||||
void checkCapable(JSON::Value &capa){
|
||||
// capa.null();
|
||||
capa.removeMember("cpu");
|
||||
std::ifstream cpuinfo("/proc/cpuinfo");
|
||||
if (cpuinfo){
|
||||
|
@ -348,29 +383,25 @@ namespace Controller {
|
|||
while (cpuinfo.good()){
|
||||
cpuinfo.getline(line, 300);
|
||||
if (cpuinfo.fail()){
|
||||
//empty lines? ignore them, clear flags, continue
|
||||
if ( !cpuinfo.eof()){
|
||||
// empty lines? ignore them, clear flags, continue
|
||||
if (!cpuinfo.eof()){
|
||||
cpuinfo.ignore();
|
||||
cpuinfo.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (memcmp(line, "processor", 9) == 0){
|
||||
proccount++;
|
||||
}
|
||||
if (memcmp(line, "processor", 9) == 0){proccount++;}
|
||||
cpus[proccount].fill(line);
|
||||
}
|
||||
//fix wrong core counts
|
||||
// fix wrong core counts
|
||||
std::map<int, int> corecounts;
|
||||
for (int i = 0; i <= proccount; ++i){
|
||||
corecounts[cpus[i].id]++;
|
||||
}
|
||||
//remove double physical IDs - we only want real CPUs.
|
||||
for (int i = 0; i <= proccount; ++i){corecounts[cpus[i].id]++;}
|
||||
// remove double physical IDs - we only want real CPUs.
|
||||
std::set<int> used_physids;
|
||||
int total_speed = 0;
|
||||
int total_threads = 0;
|
||||
for (int i = 0; i <= proccount; ++i){
|
||||
if ( !used_physids.count(cpus[i].id)){
|
||||
if (!used_physids.count(cpus[i].id)){
|
||||
used_physids.insert(cpus[i].id);
|
||||
JSON::Value thiscpu;
|
||||
thiscpu["model"] = cpus[i].model;
|
||||
|
@ -398,43 +429,37 @@ namespace Controller {
|
|||
while (meminfo.good()){
|
||||
meminfo.getline(line, 300);
|
||||
if (meminfo.fail()){
|
||||
//empty lines? ignore them, clear flags, continue
|
||||
if ( !meminfo.eof()){
|
||||
// empty lines? ignore them, clear flags, continue
|
||||
if (!meminfo.eof()){
|
||||
meminfo.ignore();
|
||||
meminfo.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
uint64_t i;
|
||||
if (sscanf(line, "MemTotal : %" PRIu64 " kB", &i) == 1){
|
||||
capa["mem"]["total"] = i / 1024;
|
||||
}
|
||||
if (sscanf(line, "MemFree : %" PRIu64 " kB", &i) == 1){
|
||||
capa["mem"]["free"] = i / 1024;
|
||||
}
|
||||
if (sscanf(line, "MemTotal : %" PRIu64 " kB", &i) == 1){capa["mem"]["total"] = i / 1024;}
|
||||
if (sscanf(line, "MemFree : %" PRIu64 " kB", &i) == 1){capa["mem"]["free"] = i / 1024;}
|
||||
if (sscanf(line, "SwapTotal : %" PRIu64 " kB", &i) == 1){
|
||||
capa["mem"]["swaptotal"] = i / 1024;
|
||||
}
|
||||
if (sscanf(line, "SwapFree : %" PRIu64 " kB", &i) == 1){
|
||||
capa["mem"]["swapfree"] = i / 1024;
|
||||
}
|
||||
if (sscanf(line, "Buffers : %" PRIu64 " kB", &i) == 1){
|
||||
bufcache += i / 1024;
|
||||
}
|
||||
if (sscanf(line, "Cached : %" PRIu64 " kB", &i) == 1){
|
||||
bufcache += i / 1024;
|
||||
}
|
||||
if (sscanf(line, "Buffers : %" PRIu64 " kB", &i) == 1){bufcache += i / 1024;}
|
||||
if (sscanf(line, "Cached : %" PRIu64 " kB", &i) == 1){bufcache += i / 1024;}
|
||||
}
|
||||
capa["mem"]["used"] = capa["mem"]["total"].asInt() - capa["mem"]["free"].asInt() - bufcache;
|
||||
capa["mem"]["cached"] = bufcache;
|
||||
capa["load"]["memory"] = ((capa["mem"]["used"].asInt() + (capa["mem"]["swaptotal"].asInt() - capa["mem"]["swapfree"].asInt())) * 100)
|
||||
/ capa["mem"]["total"].asInt();
|
||||
capa["load"]["memory"] = ((capa["mem"]["used"].asInt() +
|
||||
(capa["mem"]["swaptotal"].asInt() - capa["mem"]["swapfree"].asInt())) *
|
||||
100) /
|
||||
capa["mem"]["total"].asInt();
|
||||
}
|
||||
std::ifstream loadavg("/proc/loadavg");
|
||||
if (loadavg){
|
||||
char line[300];
|
||||
loadavg.getline(line, 300);
|
||||
//parse lines here
|
||||
// parse lines here
|
||||
float onemin, fivemin, fifteenmin;
|
||||
if (sscanf(line, "%f %f %f", &onemin, &fivemin, &fifteenmin) == 3){
|
||||
capa["load"]["one"] = uint64_t(onemin * 100);
|
||||
|
@ -448,7 +473,8 @@ namespace Controller {
|
|||
while (cpustat.getline(line, 300)){
|
||||
static uint64_t cl_total = 0, cl_idle = 0;
|
||||
uint64_t c_user, c_nice, c_syst, c_idle, c_total;
|
||||
if (sscanf(line, "cpu %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, &c_user, &c_nice, &c_syst, &c_idle) == 4){
|
||||
if (sscanf(line, "cpu %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, &c_user, &c_nice,
|
||||
&c_syst, &c_idle) == 4){
|
||||
c_total = c_user + c_nice + c_syst + c_idle;
|
||||
if (c_total - cl_total > 0){
|
||||
capa["cpu_use"] = (1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
|
||||
|
@ -461,8 +487,6 @@ namespace Controller {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}// namespace Controller
|
||||
|
|
|
@ -6,4 +6,3 @@ namespace Controller{
|
|||
void checkAvailProtocols();
|
||||
void checkAvailTriggers(); /*LTS*/
|
||||
}// namespace Controller
|
||||
|
||||
|
|
|
@ -1,32 +1,29 @@
|
|||
#include <stdio.h> // cout, cerr
|
||||
#include <string>
|
||||
#include <cstring> // strcpy
|
||||
#include <sys/stat.h> //stat
|
||||
#include <mist/json.h>
|
||||
#include "controller_connectors.h"
|
||||
#include "controller_storage.h"
|
||||
#include <cstring> // strcpy
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/procs.h>
|
||||
#include <mist/shared_memory.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/tinythread.h>
|
||||
#include <mist/defines.h>
|
||||
#include "controller_storage.h"
|
||||
#include "controller_connectors.h"
|
||||
#include <mist/triggers.h>
|
||||
#include <mist/shared_memory.h>
|
||||
#include <mist/util.h>
|
||||
#include <stdio.h> // cout, cerr
|
||||
#include <string>
|
||||
#include <sys/stat.h> //stat
|
||||
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
///\brief Holds everything unique to the controller.
|
||||
namespace Controller {
|
||||
namespace Controller{
|
||||
|
||||
static std::set<size_t> needsReload; ///< List of connector indices that needs a reload
|
||||
static std::map<std::string, pid_t> currentConnectors; ///<The currently running connectors.
|
||||
static std::map<std::string, pid_t> currentConnectors; ///< The currently running connectors.
|
||||
|
||||
void reloadProtocol(size_t indice){
|
||||
needsReload.insert(indice);
|
||||
}
|
||||
void reloadProtocol(size_t indice){needsReload.insert(indice);}
|
||||
|
||||
/// Updates the shared memory page with active connectors
|
||||
void saveActiveConnectors(bool forceOverride){
|
||||
|
@ -56,7 +53,7 @@ namespace Controller {
|
|||
++count;
|
||||
}
|
||||
A.setRCount(count);
|
||||
f.master = false;//Keep the shm page around, don't kill it
|
||||
f.master = false; // Keep the shm page around, don't kill it
|
||||
}
|
||||
|
||||
/// Reads active connectors from the shared memory pages
|
||||
|
@ -66,7 +63,7 @@ namespace Controller {
|
|||
if (A.isReady()){
|
||||
INFO_MSG("Reloading existing connectors to complete rolling restart");
|
||||
for (uint32_t i = 0; i < A.getRCount(); ++i){
|
||||
char * p = A.getPointer("cmd", i);
|
||||
char *p = A.getPointer("cmd", i);
|
||||
if (p != 0 && p[0] != 0){
|
||||
currentConnectors[p] = A.getInt("pid", i);
|
||||
Util::Procs::remember(A.getInt("pid", i));
|
||||
|
@ -80,9 +77,7 @@ namespace Controller {
|
|||
/// in preparation of shutdown.
|
||||
void prepareActiveConnectorsForShutdown(){
|
||||
IPC::sharedPage f("MstCnns", 4096, false, false);
|
||||
if (f){
|
||||
f.master = true;
|
||||
}
|
||||
if (f){f.master = true;}
|
||||
}
|
||||
|
||||
/// Forgets all active connectors, preventing them from being killed,
|
||||
|
@ -96,45 +91,46 @@ namespace Controller {
|
|||
currentConnectors.clear();
|
||||
}
|
||||
|
||||
static inline void builPipedPart(JSON::Value & p, char * argarr[], int & argnum, const JSON::Value & argset){
|
||||
jsonForEachConst(argset, it) {
|
||||
static inline void builPipedPart(JSON::Value &p, char *argarr[], int &argnum, const JSON::Value &argset){
|
||||
jsonForEachConst(argset, it){
|
||||
if (it->isMember("option") && p.isMember(it.key())){
|
||||
if (it->isMember("type")){
|
||||
if ((*it)["type"].asStringRef() == "str" && !p[it.key()].isString()){
|
||||
p[it.key()] = p[it.key()].asString();
|
||||
}
|
||||
if ((*it)["type"].asStringRef() == "uint" || (*it)["type"].asStringRef() == "int" || (*it)["type"].asStringRef() == "debug"){
|
||||
if ((*it)["type"].asStringRef() == "uint" || (*it)["type"].asStringRef() == "int" ||
|
||||
(*it)["type"].asStringRef() == "debug"){
|
||||
p[it.key()] = JSON::Value(p[it.key()].asInt()).asString();
|
||||
}
|
||||
if ((*it)["type"].asStringRef() == "inputlist" && p[it.key()].isArray()){
|
||||
jsonForEach(p[it.key()], iVal){
|
||||
(*iVal) = iVal->asString();
|
||||
argarr[argnum++] = (char*)((*it)["option"].c_str());
|
||||
argarr[argnum++] = (char*)((*iVal).c_str());
|
||||
argarr[argnum++] = (char *)((*it)["option"].c_str());
|
||||
argarr[argnum++] = (char *)((*iVal).c_str());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (p[it.key()].asStringRef().size() > 0){
|
||||
argarr[argnum++] = (char*)((*it)["option"].c_str());
|
||||
argarr[argnum++] = (char*)(p[it.key()].c_str());
|
||||
argarr[argnum++] = (char *)((*it)["option"].c_str());
|
||||
argarr[argnum++] = (char *)(p[it.key()].c_str());
|
||||
}else{
|
||||
if (it.key() == "debug"){
|
||||
if (Util::Config::printDebugLevel != DEBUG){
|
||||
static std::string debugLvlStr;
|
||||
debugLvlStr = JSON::Value(Util::Config::printDebugLevel).asString();
|
||||
argarr[argnum++] = (char*)((*it)["option"].asStringRef().c_str());
|
||||
argarr[argnum++] = (char*)debugLvlStr.c_str();
|
||||
argarr[argnum++] = (char *)((*it)["option"].asStringRef().c_str());
|
||||
argarr[argnum++] = (char *)debugLvlStr.c_str();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
argarr[argnum++] = (char*)((*it)["option"].c_str());
|
||||
argarr[argnum++] = (char *)((*it)["option"].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void buildPipedArguments(JSON::Value & p, char * argarr[], const JSON::Value & capabilities){
|
||||
|
||||
static inline void buildPipedArguments(JSON::Value &p, char *argarr[], const JSON::Value &capabilities){
|
||||
int argnum = 0;
|
||||
static std::string tmparg;
|
||||
tmparg = Util::getMyPath() + std::string("MistOut") + p["connector"].asStringRef();
|
||||
|
@ -142,21 +138,19 @@ namespace Controller {
|
|||
if (::stat(tmparg.c_str(), &buf) != 0){
|
||||
tmparg = Util::getMyPath() + std::string("MistConn") + p["connector"].asStringRef();
|
||||
}
|
||||
if (::stat(tmparg.c_str(), &buf) != 0){
|
||||
return;
|
||||
}
|
||||
argarr[argnum++] = (char*)tmparg.c_str();
|
||||
const JSON::Value & pipedCapa = capabilities["connectors"][p["connector"].asStringRef()];
|
||||
if (::stat(tmparg.c_str(), &buf) != 0){return;}
|
||||
argarr[argnum++] = (char *)tmparg.c_str();
|
||||
const JSON::Value &pipedCapa = capabilities["connectors"][p["connector"].asStringRef()];
|
||||
if (pipedCapa.isMember("required")){builPipedPart(p, argarr, argnum, pipedCapa["required"]);}
|
||||
if (pipedCapa.isMember("optional")){builPipedPart(p, argarr, argnum, pipedCapa["optional"]);}
|
||||
}
|
||||
|
||||
|
||||
///\brief Checks current protocol configuration, updates state of enabled connectors if neccessary.
|
||||
///\param p An object containing all protocols.
|
||||
///\param capabilities An object containing the detected capabilities.
|
||||
///\returns True if any action was taken
|
||||
///
|
||||
/// \triggers
|
||||
///
|
||||
/// \triggers
|
||||
/// The `"OUTPUT_START"` trigger is global, and is ran whenever a new protocol listener is started. It cannot be cancelled. Its payload is:
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// output listener commandline
|
||||
|
@ -165,48 +159,49 @@ namespace Controller {
|
|||
/// ~~~~~~~~~~~~~~~
|
||||
/// output listener commandline
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
bool CheckProtocols(JSON::Value & p, const JSON::Value & capabilities){
|
||||
bool CheckProtocols(JSON::Value &p, const JSON::Value &capabilities){
|
||||
std::set<std::string> runningConns;
|
||||
|
||||
// used for building args
|
||||
int zero = 0;
|
||||
int out = fileno(stdout);
|
||||
int err = fileno(stderr);
|
||||
char * argarr[15]; // approx max # of args (with a wide margin)
|
||||
char *argarr[15]; // approx max # of args (with a wide margin)
|
||||
int i;
|
||||
|
||||
std::string tmp;
|
||||
|
||||
jsonForEach(p, ait) {
|
||||
jsonForEach(p, ait){
|
||||
std::string prevOnline = (*ait)["online"].asString();
|
||||
const std::string & connName = (*ait)["connector"].asStringRef();
|
||||
//do not further parse if there's no connector name
|
||||
if ( !(*ait).isMember("connector") || connName == ""){
|
||||
( *ait)["online"] = "Missing connector name";
|
||||
const std::string &connName = (*ait)["connector"].asStringRef();
|
||||
// do not further parse if there's no connector name
|
||||
if (!(*ait).isMember("connector") || connName == ""){
|
||||
(*ait)["online"] = "Missing connector name";
|
||||
continue;
|
||||
}
|
||||
//ignore connectors that are not installed
|
||||
// ignore connectors that are not installed
|
||||
if (!capabilities.isMember("connectors") || !capabilities["connectors"].isMember(connName)){
|
||||
( *ait)["online"] = "Not installed";
|
||||
if (( *ait)["online"].asString() != prevOnline){
|
||||
Log("WARN", connName + " connector is enabled but doesn't exist on system! Ignoring connector.");
|
||||
(*ait)["online"] = "Not installed";
|
||||
if ((*ait)["online"].asString() != prevOnline){
|
||||
Log("WARN",
|
||||
connName + " connector is enabled but doesn't exist on system! Ignoring connector.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
//list connectors that go through HTTP as 'enabled' without actually running them.
|
||||
const JSON::Value & connCapa = capabilities["connectors"][connName];
|
||||
// list connectors that go through HTTP as 'enabled' without actually running them.
|
||||
const JSON::Value &connCapa = capabilities["connectors"][connName];
|
||||
if (connCapa.isMember("socket") || (connCapa.isMember("deps") && connCapa["deps"].asStringRef() == "HTTP")){
|
||||
( *ait)["online"] = "Enabled";
|
||||
(*ait)["online"] = "Enabled";
|
||||
continue;
|
||||
}
|
||||
//check required parameters, skip if anything is missing
|
||||
// check required parameters, skip if anything is missing
|
||||
if (connCapa.isMember("required")){
|
||||
bool gotAll = true;
|
||||
jsonForEachConst(connCapa["required"], it) {
|
||||
if ( !(*ait).isMember(it.key()) || (*ait)[it.key()].asStringRef().size() < 1){
|
||||
jsonForEachConst(connCapa["required"], it){
|
||||
if (!(*ait).isMember(it.key()) || (*ait)[it.key()].asStringRef().size() < 1){
|
||||
gotAll = false;
|
||||
( *ait)["online"] = "Invalid configuration";
|
||||
if (( *ait)["online"].asString() != prevOnline){
|
||||
(*ait)["online"] = "Invalid configuration";
|
||||
if ((*ait)["online"].asString() != prevOnline){
|
||||
Log("WARN", connName + " connector is missing required parameter " + it.key() + "! Ignoring connector.");
|
||||
}
|
||||
break;
|
||||
|
@ -214,26 +209,26 @@ namespace Controller {
|
|||
}
|
||||
if (!gotAll){continue;}
|
||||
}
|
||||
//remove current online status
|
||||
( *ait).removeMember("online");
|
||||
// remove current online status
|
||||
(*ait).removeMember("online");
|
||||
/// \todo Check dependencies?
|
||||
//set current online status
|
||||
// set current online status
|
||||
std::string myCmd = (*ait).toString();
|
||||
runningConns.insert(myCmd);
|
||||
if (currentConnectors.count(myCmd) && Util::Procs::isActive(currentConnectors[myCmd])){
|
||||
( *ait)["online"] = 1;
|
||||
//Reload connectors that need it
|
||||
(*ait)["online"] = 1;
|
||||
// Reload connectors that need it
|
||||
if (needsReload.count(ait.num())){
|
||||
kill(currentConnectors[myCmd], SIGUSR1);
|
||||
needsReload.erase(ait.num());
|
||||
}
|
||||
}else{
|
||||
( *ait)["online"] = 0;
|
||||
(*ait)["online"] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool action = false;
|
||||
//shut down deleted/changed connectors
|
||||
// shut down deleted/changed connectors
|
||||
std::map<std::string, pid_t>::iterator it;
|
||||
if (currentConnectors.size()){
|
||||
for (it = currentConnectors.begin(); it != currentConnectors.end(); it++){
|
||||
|
@ -242,30 +237,29 @@ namespace Controller {
|
|||
Log("CONF", "Stopping connector " + it->first);
|
||||
action = true;
|
||||
Util::Procs::Stop(it->second);
|
||||
Triggers::doTrigger("OUTPUT_STOP",it->first); //LTS
|
||||
Triggers::doTrigger("OUTPUT_STOP", it->first); // LTS
|
||||
}
|
||||
currentConnectors.erase(it);
|
||||
if (!currentConnectors.size()){
|
||||
break;
|
||||
}
|
||||
if (!currentConnectors.size()){break;}
|
||||
it = currentConnectors.begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//start up new/changed connectors
|
||||
// start up new/changed connectors
|
||||
while (runningConns.size() && conf.is_active){
|
||||
if (!currentConnectors.count(*runningConns.begin()) || !Util::Procs::isActive(currentConnectors[*runningConns.begin()])){
|
||||
if (!currentConnectors.count(*runningConns.begin()) ||
|
||||
!Util::Procs::isActive(currentConnectors[*runningConns.begin()])){
|
||||
Log("CONF", "Starting connector: " + *runningConns.begin());
|
||||
action = true;
|
||||
// clear out old args
|
||||
for (i=0; i<15; i++){argarr[i] = 0;}
|
||||
for (i = 0; i < 15; i++){argarr[i] = 0;}
|
||||
// get args for this connector
|
||||
JSON::Value p = JSON::fromString(*runningConns.begin());
|
||||
buildPipedArguments(p, (char **)&argarr, capabilities);
|
||||
// start piped w/ generated args
|
||||
currentConnectors[*runningConns.begin()] = Util::Procs::StartPiped(argarr, &zero, &out, &err);
|
||||
Triggers::doTrigger("OUTPUT_START", *runningConns.begin());//LTS
|
||||
Triggers::doTrigger("OUTPUT_START", *runningConns.begin()); // LTS
|
||||
}
|
||||
runningConns.erase(runningConns.begin());
|
||||
}
|
||||
|
@ -273,5 +267,4 @@ namespace Controller {
|
|||
return action;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}// namespace Controller
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#include <mist/json.h>
|
||||
|
||||
namespace Controller {
|
||||
namespace Controller{
|
||||
|
||||
/// Marks the given protocol as needing a reload (signal USR1) on next check
|
||||
void reloadProtocol(size_t indice);
|
||||
|
||||
/// Checks current protocol configuration, updates state of enabled connectors if neccesary.
|
||||
bool CheckProtocols(JSON::Value & p, const JSON::Value & capabilities);
|
||||
bool CheckProtocols(JSON::Value &p, const JSON::Value &capabilities);
|
||||
|
||||
/// Updates the shared memory page with active connectors
|
||||
void saveActiveConnectors(bool forceOverride = false);
|
||||
|
@ -14,7 +14,6 @@ namespace Controller {
|
|||
/// Reads active connectors from the shared memory pages
|
||||
void loadActiveConnectors();
|
||||
|
||||
|
||||
/// Deletes the shared memory page with connector information
|
||||
/// in preparation of shutdown.
|
||||
void prepareActiveConnectorsForShutdown();
|
||||
|
@ -23,5 +22,4 @@ namespace Controller {
|
|||
/// in preparation of reload.
|
||||
void prepareActiveConnectorsForReload();
|
||||
|
||||
}
|
||||
|
||||
}// namespace Controller
|
||||
|
|
|
@ -1,32 +1,29 @@
|
|||
#include "controller_license.h"
|
||||
#include "controller_storage.h"
|
||||
#include <iostream>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/downloader.h>
|
||||
#include <mist/encode.h>
|
||||
#include <mist/encryption.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/encryption.h>
|
||||
#include <mist/encode.h>
|
||||
#include <mist/downloader.h>
|
||||
|
||||
|
||||
namespace Controller{
|
||||
|
||||
|
||||
uint64_t exitDelay = 0;
|
||||
static JSON::Value currentLicense;
|
||||
static uint64_t lastCheck = 0;
|
||||
static int32_t timeOffset = 0;
|
||||
static bool everContactedServer = false;
|
||||
|
||||
const JSON::Value & getLicense(){
|
||||
return currentLicense;
|
||||
}
|
||||
|
||||
//PACKAGE_VERSION = MistServer version
|
||||
//RELEASE = OS + user_ID
|
||||
|
||||
|
||||
const JSON::Value &getLicense(){return currentLicense;}
|
||||
|
||||
// PACKAGE_VERSION = MistServer version
|
||||
// RELEASE = OS + user_ID
|
||||
|
||||
void initLicense(){
|
||||
if (Storage.isMember("license") && Storage.isMember("license_id")){
|
||||
INFO_MSG("Reading license from storage")
|
||||
|
@ -48,30 +45,30 @@ namespace Controller{
|
|||
#if DEBUG >= DLVL_DEVEL
|
||||
INFO_MSG("Verifying license against %" PRIu64 ": %s", now, currentLicense.toString().c_str());
|
||||
#endif
|
||||
//Print messages for user, if any
|
||||
// Print messages for user, if any
|
||||
if (currentLicense.isMember("user_msg") && currentLicense["user_msg"].asStringRef().size()){
|
||||
WARN_MSG("%s", currentLicense["user_msg"].asStringRef().c_str());
|
||||
}
|
||||
//Check time
|
||||
if (!currentLicense.isMember("valid_from") || !currentLicense.isMember("valid_till") || now < currentLicense["valid_from"].asInt() || now > currentLicense["valid_till"].asInt()){
|
||||
return false;//license is expired
|
||||
// Check time
|
||||
if (!currentLicense.isMember("valid_from") || !currentLicense.isMember("valid_till") ||
|
||||
now < currentLicense["valid_from"].asInt() || now > currentLicense["valid_till"].asInt()){
|
||||
return false; // license is expired
|
||||
}
|
||||
//Check release/version
|
||||
if (RELEASE != currentLicense["release"].asStringRef() || PACKAGE_VERSION != currentLicense["version"].asStringRef()){
|
||||
// Check release/version
|
||||
if (RELEASE != currentLicense["release"].asStringRef() ||
|
||||
PACKAGE_VERSION != currentLicense["version"].asStringRef()){
|
||||
FAIL_MSG("Could not verify license");
|
||||
return false;
|
||||
}
|
||||
//everything seems okay
|
||||
// everything seems okay
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool checkLicense(){
|
||||
if (!conf.is_active){return true;}
|
||||
INFO_MSG("Checking license validity");
|
||||
if(!everContactedServer && !isLicensed()){
|
||||
updateLicense("&expired=1");
|
||||
}
|
||||
if(!isLicensed()){
|
||||
if (!everContactedServer && !isLicensed()){updateLicense("&expired=1");}
|
||||
if (!isLicensed()){
|
||||
FAIL_MSG("Not licensed, shutting down");
|
||||
if (currentLicense.isMember("delay") && currentLicense["delay"].asInt()){
|
||||
exitDelay = currentLicense["delay"].asInt();
|
||||
|
@ -83,79 +80,84 @@ namespace Controller{
|
|||
lastCheck = Util::epoch();
|
||||
return true;
|
||||
}
|
||||
|
||||
void parseKey(std::string key, char * newKey, unsigned int len){
|
||||
|
||||
void parseKey(std::string key, char *newKey, unsigned int len){
|
||||
memset(newKey, 0, len);
|
||||
for (size_t i = 0; i < key.size() && i < (len << 1); ++i){
|
||||
char c = key[i];
|
||||
newKey[i>>1] |= ((c&15) + (((c&64)>>6) | ((c&64)>>3))) << ((~i&1) << 2);
|
||||
newKey[i >> 1] |= ((c & 15) + (((c & 64) >> 6) | ((c & 64) >> 3))) << ((~i & 1) << 2);
|
||||
}
|
||||
}
|
||||
|
||||
void updateLicense(const std::string & extra){
|
||||
|
||||
void updateLicense(const std::string &extra){
|
||||
INFO_MSG("Running license updater %s", extra.c_str());
|
||||
JSON::Value response;
|
||||
|
||||
|
||||
HTTP::Downloader dl;
|
||||
dl.dataTimeout = 25;//25-second data timeout, increased from 5s default
|
||||
dl.dataTimeout = 25; // 25-second data timeout, increased from 5s default
|
||||
#ifdef SSL
|
||||
HTTP::URL url("https://releases.mistserver.org/license.php");
|
||||
if (dl.isProxied()){url.protocol = "http";}
|
||||
#else
|
||||
HTTP::URL url("http://releases.mistserver.org/license.php");
|
||||
#endif
|
||||
url.args = "release="+Encodings::URL::encode(RELEASE)+"&version="+Encodings::URL::encode(PACKAGE_VERSION)+"&iid="+Encodings::URL::encode(instanceId)+"&hrn="+Encodings::URL::encode(Storage["config"]["serverid"])+"&lid="+currentLicense["lic_id"].asString() + extra;
|
||||
url.args = "release=" + Encodings::URL::encode(RELEASE) +
|
||||
"&version=" + Encodings::URL::encode(PACKAGE_VERSION) +
|
||||
"&iid=" + Encodings::URL::encode(instanceId) +
|
||||
"&hrn=" + Encodings::URL::encode(Storage["config"]["serverid"]) +
|
||||
"&lid=" + currentLicense["lic_id"].asString() + extra;
|
||||
|
||||
long long currID = currentLicense["lic_id"].asInt();
|
||||
if (currID){
|
||||
char aesKey[16];
|
||||
if (strlen(SUPER_SECRET) >= 32){
|
||||
parseKey((SUPER_SECRET SUPER_SECRET)+7,aesKey,16);
|
||||
parseKey((SUPER_SECRET SUPER_SECRET) + 7, aesKey, 16);
|
||||
}else{
|
||||
parseKey("4E56721C67306E1F473156F755FF5570",aesKey,16);
|
||||
parseKey("4E56721C67306E1F473156F755FF5570", aesKey, 16);
|
||||
}
|
||||
for (unsigned int i = 0; i < 8; ++i){
|
||||
aesKey[15-i] = ((currID >> (i*8)) + aesKey[15-i]) & 0xFF;
|
||||
aesKey[15 - i] = ((currID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
|
||||
}
|
||||
char ivec[16];
|
||||
memset(ivec, 0, 16);
|
||||
dl.setHeader("X-IRDGAF", Encodings::Base64::encode(Encryption::AES_Crypt(RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec)));
|
||||
}
|
||||
if (!dl.get(url) || !dl.isOk()){
|
||||
return;
|
||||
dl.setHeader("X-IRDGAF",
|
||||
Encodings::Base64::encode(Encryption::AES_Crypt(
|
||||
RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec)));
|
||||
}
|
||||
if (!dl.get(url) || !dl.isOk()){return;}
|
||||
response = JSON::fromString(dl.data());
|
||||
everContactedServer = true;
|
||||
|
||||
//read license
|
||||
|
||||
// read license
|
||||
readLicense(response["lic_id"].asInt(), response["license"].asStringRef(), true);
|
||||
}
|
||||
|
||||
void readLicense(uint64_t licID, const std::string & input, bool fromServer){
|
||||
|
||||
void readLicense(uint64_t licID, const std::string &input, bool fromServer){
|
||||
char aesKey[16];
|
||||
if (strlen(SUPER_SECRET) >= 32){
|
||||
parseKey((SUPER_SECRET SUPER_SECRET)+ 7,aesKey,16);
|
||||
parseKey((SUPER_SECRET SUPER_SECRET) + 7, aesKey, 16);
|
||||
}else{
|
||||
parseKey("4E56721C67306E1F473156F755FF5570",aesKey,16);
|
||||
parseKey("4E56721C67306E1F473156F755FF5570", aesKey, 16);
|
||||
}
|
||||
for (unsigned int i = 0; i < 8; ++i){
|
||||
aesKey[15-i] = ((licID >> (i*8)) + aesKey[15-i]) & 0xFF;
|
||||
aesKey[15 - i] = ((licID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
|
||||
}
|
||||
std::string cipher = Encodings::Base64::decode(input);
|
||||
std::string deCrypted;
|
||||
//magic ivecs, they are empty. It's secretly 16 times \0.
|
||||
// magic ivecs, they are empty. It's secretly 16 times \0.
|
||||
char ivec[16];
|
||||
memset(ivec, 0, 16);
|
||||
deCrypted = Encryption::AES_Crypt(cipher.c_str(), cipher.size(), aesKey, ivec);
|
||||
//get time stamps and license.
|
||||
|
||||
//verify checksum
|
||||
if (deCrypted.size() < 33 || Secure::md5(deCrypted.substr(32)) != deCrypted.substr(0,32)){
|
||||
// get time stamps and license.
|
||||
|
||||
// verify checksum
|
||||
if (deCrypted.size() < 33 || Secure::md5(deCrypted.substr(32)) != deCrypted.substr(0, 32)){
|
||||
WARN_MSG("Could not decode license");
|
||||
return;
|
||||
}
|
||||
JSON::Value newLicense = JSON::fromString(deCrypted.substr(32));
|
||||
if (RELEASE != newLicense["release"].asStringRef() || PACKAGE_VERSION != newLicense["version"].asStringRef()){
|
||||
if (RELEASE != newLicense["release"].asStringRef() ||
|
||||
PACKAGE_VERSION != newLicense["version"].asStringRef()){
|
||||
FAIL_MSG("Could not verify license");
|
||||
return;
|
||||
}
|
||||
|
@ -164,10 +166,14 @@ namespace Controller{
|
|||
uint64_t localTime = Util::epoch();
|
||||
uint64_t remoteTime = newLicense["time"].asInt();
|
||||
if (localTime > remoteTime + 60){
|
||||
WARN_MSG("Your computer clock is %" PRIu64 " seconds ahead! Please ensure your computer clock is set correctly.", localTime - remoteTime);
|
||||
WARN_MSG("Your computer clock is %" PRIu64
|
||||
" seconds ahead! Please ensure your computer clock is set correctly.",
|
||||
localTime - remoteTime);
|
||||
}
|
||||
if (localTime < remoteTime - 60){
|
||||
WARN_MSG("Your computer clock is %" PRIu64 " seconds late! Please ensure your computer clock is set correctly.", remoteTime - localTime);
|
||||
WARN_MSG("Your computer clock is %" PRIu64
|
||||
" seconds late! Please ensure your computer clock is set correctly.",
|
||||
remoteTime - localTime);
|
||||
}
|
||||
timeOffset = remoteTime - localTime;
|
||||
|
||||
|
@ -179,7 +185,7 @@ namespace Controller{
|
|||
|
||||
currentLicense = newLicense;
|
||||
|
||||
//Store license here.
|
||||
// Store license here.
|
||||
if (currentLicense["store"].asBool()){
|
||||
if (Storage["license"].asStringRef() != input){
|
||||
Storage["license"] = input;
|
||||
|
@ -188,21 +194,16 @@ namespace Controller{
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void licenseLoop(void * np){
|
||||
|
||||
void licenseLoop(void *np){
|
||||
while (conf.is_active){
|
||||
uint64_t interval = currentLicense["interval"].asInt();
|
||||
if (Util::epoch() - lastCheck > (interval?interval:3600)){
|
||||
if (interval){
|
||||
updateLicense();
|
||||
}
|
||||
if (Util::epoch() - lastCheck > (interval ? interval : 3600)){
|
||||
if (interval){updateLicense();}
|
||||
checkLicense();
|
||||
}
|
||||
Util::sleep(1000);//sleep a bit
|
||||
}
|
||||
if (everContactedServer){
|
||||
updateLicense("&shutdown=1");
|
||||
Util::sleep(1000); // sleep a bit
|
||||
}
|
||||
if (everContactedServer){updateLicense("&shutdown=1");}
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace Controller
|
||||
|
|
|
@ -3,16 +3,13 @@
|
|||
|
||||
namespace Controller{
|
||||
extern uint64_t exitDelay;
|
||||
|
||||
const JSON::Value & getLicense();
|
||||
|
||||
const JSON::Value &getLicense();
|
||||
void initLicense();
|
||||
bool isLicensed(); //checks/verifies license time
|
||||
bool checkLicense(); //Call from Mainloop.
|
||||
void updateLicense(const std::string & extra = ""); //retrieves update from license server
|
||||
void licenseLoop(void * np);
|
||||
void readLicense(uint64_t licId, const std::string & input, bool fromServer = false); //checks/interprets license
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
bool isLicensed(); // checks/verifies license time
|
||||
bool checkLicense(); // Call from Mainloop.
|
||||
void updateLicense(const std::string &extra = ""); // retrieves update from license server
|
||||
void licenseLoop(void *np);
|
||||
void readLicense(uint64_t licId, const std::string &input, bool fromServer = false); // checks/interprets license
|
||||
|
||||
}// namespace Controller
|
||||
|
|
|
@ -2,39 +2,35 @@
|
|||
#include "controller_statistics.h"
|
||||
#include "controller_storage.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <iostream>
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace Controller{
|
||||
void checkStreamLimits(std::string streamName, long long currentKbps, long long connectedUsers){
|
||||
if( !Storage["streams"].isMember(streamName)){
|
||||
return;
|
||||
}
|
||||
if( !Storage["streams"][streamName].isMember("limits")){
|
||||
return;
|
||||
}
|
||||
if( !Storage["streams"][streamName]["limits"]){
|
||||
return;
|
||||
}
|
||||
if (!Storage["streams"].isMember(streamName)){return;}
|
||||
if (!Storage["streams"][streamName].isMember("limits")){return;}
|
||||
if (!Storage["streams"][streamName]["limits"]){return;}
|
||||
|
||||
Storage["streams"][streamName].removeMember("hardlimit_active");
|
||||
if (Storage["streams"][streamName]["online"].asInt() != 1){
|
||||
jsonForEach(Storage["streams"][streamName]["limits"], limitIt){
|
||||
if ((*limitIt).isMember("triggered")){
|
||||
if ((*limitIt)["type"].asString() == "soft"){
|
||||
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset - stream unavailable.");
|
||||
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
|
||||
" for stream " + streamName + " reset - stream unavailable.");
|
||||
}else{
|
||||
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset - stream unavailable.");
|
||||
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
|
||||
" for stream " + streamName + " reset - stream unavailable.");
|
||||
}
|
||||
(*limitIt).removeMember("triggered");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//run over all limits.
|
||||
|
||||
// run over all limits.
|
||||
jsonForEach(Storage["streams"][streamName]["limits"], limitIt){
|
||||
bool triggerLimit = false;
|
||||
if ((*limitIt)["name"].asString() == "users" && connectedUsers >= (*limitIt)["value"].asInt()){
|
||||
|
@ -47,23 +43,23 @@ namespace Controller{
|
|||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Storage["streams"][streamName]["hardlimit_active"] = true;
|
||||
}
|
||||
if ((*limitIt).isMember("triggered")){
|
||||
continue;
|
||||
}
|
||||
if ((*limitIt).isMember("triggered")){continue;}
|
||||
if ((*limitIt)["type"].asString() == "soft"){
|
||||
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " triggered.");
|
||||
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
|
||||
" for stream " + streamName + " triggered.");
|
||||
}else{
|
||||
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " triggered.");
|
||||
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
|
||||
" for stream " + streamName + " triggered.");
|
||||
}
|
||||
(*limitIt)["triggered"] = true;
|
||||
}else{
|
||||
if ( !(*limitIt).isMember("triggered")){
|
||||
continue;
|
||||
}
|
||||
if (!(*limitIt).isMember("triggered")){continue;}
|
||||
if ((*limitIt)["type"].asString() == "soft"){
|
||||
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
|
||||
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() +
|
||||
" <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
|
||||
}else{
|
||||
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
|
||||
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() +
|
||||
" <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
|
||||
}
|
||||
(*limitIt).removeMember("triggered");
|
||||
}
|
||||
|
@ -71,12 +67,12 @@ namespace Controller{
|
|||
}
|
||||
|
||||
void checkServerLimits(){
|
||||
|
||||
|
||||
int currentKbps = 0;
|
||||
int connectedUsers = 0;
|
||||
std::map<std::string, long long> strmUsers;
|
||||
std::map<std::string, long long> strmBandw;
|
||||
|
||||
|
||||
/*
|
||||
if (curConns.size()){
|
||||
for (std::map<unsigned long, statStorage>::iterator it = curConns.begin(); it != curConns.end(); it++){
|
||||
|
@ -91,22 +87,18 @@ namespace Controller{
|
|||
}
|
||||
}
|
||||
*/
|
||||
|
||||
//check stream limits
|
||||
|
||||
// check stream limits
|
||||
if (Storage["streams"].size()){
|
||||
jsonForEach(Storage["streams"], strmIt){
|
||||
checkStreamLimits(strmIt.key(), strmBandw[strmIt.key()], strmUsers[strmIt.key()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Storage["config"].removeMember("hardlimit_active");
|
||||
if ( !Storage["config"]["limits"].size()){
|
||||
return;
|
||||
}
|
||||
if ( !Storage["streams"].size()){
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Storage["config"]["limits"].size()){return;}
|
||||
if (!Storage["streams"].size()){return;}
|
||||
|
||||
jsonForEach(Storage["config"]["limits"], limitIt){
|
||||
bool triggerLimit = false;
|
||||
if ((*limitIt)["name"].asString() == "users" && connectedUsers >= (*limitIt)["value"].asInt()){
|
||||
|
@ -119,66 +111,52 @@ namespace Controller{
|
|||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Storage["config"]["hardlimit_active"] = true;
|
||||
}
|
||||
if ((*limitIt).isMember("triggered")){
|
||||
continue;
|
||||
}
|
||||
if ((*limitIt).isMember("triggered")){continue;}
|
||||
if ((*limitIt)["type"].asString() == "soft"){
|
||||
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " triggered.");
|
||||
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() +
|
||||
" <= " + (*limitIt)["value"].asString() + " triggered.");
|
||||
}else{
|
||||
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " triggered.");
|
||||
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() +
|
||||
" <= " + (*limitIt)["value"].asString() + " triggered.");
|
||||
}
|
||||
(*limitIt)["triggered"] = true;
|
||||
}else{
|
||||
if ( !(*limitIt).isMember("triggered")){
|
||||
continue;
|
||||
}
|
||||
if (!(*limitIt).isMember("triggered")){continue;}
|
||||
if ((*limitIt)["type"].asString() == "soft"){
|
||||
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " reset.");
|
||||
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() +
|
||||
" <= " + (*limitIt)["value"].asString() + " reset.");
|
||||
}else{
|
||||
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " reset.");
|
||||
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() +
|
||||
" <= " + (*limitIt)["value"].asString() + " reset.");
|
||||
}
|
||||
(*limitIt).removeMember("triggered");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool onList(std::string ip, std::string list){
|
||||
if (list == ""){
|
||||
return false;
|
||||
}
|
||||
if (list == ""){return false;}
|
||||
std::string entry;
|
||||
std::string lowerIpv6;//lower-case
|
||||
std::string upperIpv6;//full-caps
|
||||
std::string lowerIpv6; // lower-case
|
||||
std::string upperIpv6; // full-caps
|
||||
do{
|
||||
entry = list.substr(0,list.find(" "));//make sure we have a single entry
|
||||
entry = list.substr(0, list.find(" ")); // make sure we have a single entry
|
||||
lowerIpv6 = "::ffff:" + entry;
|
||||
upperIpv6 = "::FFFF:" + entry;
|
||||
if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){
|
||||
return true;
|
||||
}
|
||||
if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){return true;}
|
||||
long long unsigned int starPos = entry.find("*");
|
||||
if (starPos == std::string::npos){
|
||||
if (ip == entry){
|
||||
return true;
|
||||
}
|
||||
if (ip == entry){return true;}
|
||||
}else{
|
||||
if (starPos == 0){//beginning of the filter
|
||||
if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){
|
||||
return true;
|
||||
}
|
||||
if (starPos == 0){// beginning of the filter
|
||||
if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){return true;}
|
||||
}else{
|
||||
if (starPos == entry.size() - 1){//end of the filter
|
||||
if (ip.find(entry.substr(0, entry.size() - 1)) == 0 ){
|
||||
return true;
|
||||
}
|
||||
if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0 ){
|
||||
return true;
|
||||
}
|
||||
if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0 ){
|
||||
return true;
|
||||
}
|
||||
if (starPos == entry.size() - 1){// end of the filter
|
||||
if (ip.find(entry.substr(0, entry.size() - 1)) == 0){return true;}
|
||||
if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0){return true;}
|
||||
if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0){return true;}
|
||||
}else{
|
||||
Log("CONF","Invalid list entry detected: " + entry);
|
||||
Log("CONF", "Invalid list entry detected: " + entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -186,44 +164,38 @@ namespace Controller{
|
|||
}while (list != "");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::string hostLookup(std::string ip){
|
||||
struct sockaddr_in6 sa;
|
||||
char hostName[1024];
|
||||
char service[20];
|
||||
if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){
|
||||
return "\n";
|
||||
}
|
||||
if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){return "\n";}
|
||||
sa.sin6_family = AF_INET6;
|
||||
sa.sin6_port = 0;
|
||||
sa.sin6_flowinfo = 0;
|
||||
sa.sin6_scope_id = 0;
|
||||
int tmpRet = getnameinfo((struct sockaddr*)&sa, sizeof sa, hostName, sizeof hostName, service, sizeof service, NI_NAMEREQD );
|
||||
if ( tmpRet == 0){
|
||||
return hostName;
|
||||
}
|
||||
int tmpRet = getnameinfo((struct sockaddr *)&sa, sizeof sa, hostName, sizeof hostName, service,
|
||||
sizeof service, NI_NAMEREQD);
|
||||
if (tmpRet == 0){return hostName;}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
bool isBlacklisted(std::string host, std::string streamName, int timeConnected){
|
||||
std::string myHostName = hostLookup(host);
|
||||
if (myHostName == "\n"){
|
||||
return false;
|
||||
}
|
||||
if (myHostName == "\n"){return false;}
|
||||
bool hasWhitelist = false;
|
||||
bool hostOnWhitelist = false;
|
||||
if (Storage["streams"].isMember(streamName)){
|
||||
if (Storage["streams"][streamName].isMember("limits") && Storage["streams"][streamName]["limits"].size()){
|
||||
if (Storage["streams"][streamName].isMember("limits") &&
|
||||
Storage["streams"][streamName]["limits"].size()){
|
||||
jsonForEach(Storage["streams"][streamName]["limits"], limitIt){
|
||||
if ((*limitIt)["name"].asString() == "host"){
|
||||
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||
if (myHostName == ""){
|
||||
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
|
||||
return true;
|
||||
}
|
||||
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){return true;}
|
||||
}else{
|
||||
if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if (!onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||
return true;
|
||||
|
@ -263,11 +235,9 @@ namespace Controller{
|
|||
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||
if (myHostName == ""){
|
||||
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
|
||||
return true;
|
||||
}
|
||||
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){return true;}
|
||||
}else{
|
||||
if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if (!onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||
return true;
|
||||
|
@ -310,4 +280,4 @@ namespace Controller{
|
|||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}// namespace Controller
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
#include <mist/json.h>
|
||||
#include <map>
|
||||
#include <mist/json.h>
|
||||
#include <string>
|
||||
|
||||
/*LTS-START*/
|
||||
|
@ -18,4 +18,4 @@ namespace Controller{
|
|||
std::string hostLookup(std::string ip);
|
||||
bool onList(std::string ip, std::string list);
|
||||
std::string getCountry(std::string ip);
|
||||
}
|
||||
}// namespace Controller
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Controller{
|
|||
/// Immediately starts a push for the given stream to the given target.
|
||||
/// Simply calls Util::startPush and stores the resulting PID in the local activePushes map.
|
||||
void startPush(const std::string &stream, std::string &target){
|
||||
//Cancel if already active
|
||||
// Cancel if already active
|
||||
if (isPushActive(stream, target)){return;}
|
||||
std::string originalTarget = target;
|
||||
pid_t ret = Util::startPush(stream, target);
|
||||
|
@ -40,13 +40,13 @@ namespace Controller{
|
|||
|
||||
/// Returns true if the push is currently active, false otherwise.
|
||||
bool isPushActive(const std::string &streamname, const std::string &target){
|
||||
while (Controller::conf.is_active && !pushListRead){
|
||||
Util::sleep(100);
|
||||
}
|
||||
while (Controller::conf.is_active && !pushListRead){Util::sleep(100);}
|
||||
std::set<pid_t> toWipe;
|
||||
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
|
||||
if (Util::Procs::isActive(it->first)){
|
||||
if (it->second[1u].asStringRef() == streamname && it->second[2u].asStringRef() == target){return true;}
|
||||
if (it->second[1u].asStringRef() == streamname && it->second[2u].asStringRef() == target){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
toWipe.insert(it->first);
|
||||
}
|
||||
|
@ -61,13 +61,13 @@ namespace Controller{
|
|||
|
||||
/// Stops any pushes matching the stream name (pattern) and target
|
||||
void stopActivePushes(const std::string &streamname, const std::string &target){
|
||||
while (Controller::conf.is_active && !pushListRead){
|
||||
Util::sleep(100);
|
||||
}
|
||||
while (Controller::conf.is_active && !pushListRead){Util::sleep(100);}
|
||||
std::set<pid_t> toWipe;
|
||||
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
|
||||
if (Util::Procs::isActive(it->first)){
|
||||
if (it->second[2u].asStringRef() == target && (it->second[1u].asStringRef() == streamname || (*streamname.rbegin() == '+' && it->second[1u].asStringRef().substr(0, streamname.size()) == streamname))){
|
||||
if (it->second[2u].asStringRef() == target &&
|
||||
(it->second[1u].asStringRef() == streamname ||
|
||||
(*streamname.rbegin() == '+' && it->second[1u].asStringRef().substr(0, streamname.size()) == streamname))){
|
||||
Util::Procs::Stop(it->first);
|
||||
}
|
||||
}else{
|
||||
|
@ -87,31 +87,30 @@ namespace Controller{
|
|||
}
|
||||
|
||||
/// Compactly writes the list of pushes to a pointer, assumed to be 8MiB in size
|
||||
static void writePushList(char * pwo){
|
||||
char * max = pwo + 8*1024*1024 - 4;
|
||||
static void writePushList(char *pwo){
|
||||
char *max = pwo + 8 * 1024 * 1024 - 4;
|
||||
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
|
||||
//check if the whole entry will fit
|
||||
unsigned int entrylen = 4+2+it->second[1u].asStringRef().size()+2+it->second[2u].asStringRef().size()+2+it->second[3u].asStringRef().size();
|
||||
if (pwo+entrylen >= max){return;}
|
||||
//write the pid as a 32 bits unsigned integer
|
||||
// check if the whole entry will fit
|
||||
unsigned int entrylen = 4 + 2 + it->second[1u].asStringRef().size() + 2 +
|
||||
it->second[2u].asStringRef().size() + 2 + it->second[3u].asStringRef().size();
|
||||
if (pwo + entrylen >= max){return;}
|
||||
// write the pid as a 32 bits unsigned integer
|
||||
Bit::htobl(pwo, it->first);
|
||||
pwo += 4;
|
||||
//write the streamname, original target and target, 2-byte-size-prepended
|
||||
// write the streamname, original target and target, 2-byte-size-prepended
|
||||
for (unsigned int i = 1; i < 4; ++i){
|
||||
const std::string &itm = it->second[i].asStringRef();
|
||||
Bit::htobs(pwo, itm.size());
|
||||
memcpy(pwo+2, itm.data(), itm.size());
|
||||
pwo += 2+itm.size();
|
||||
memcpy(pwo + 2, itm.data(), itm.size());
|
||||
pwo += 2 + itm.size();
|
||||
}
|
||||
}
|
||||
//if it fits, write an ending zero to indicate end of page
|
||||
if (pwo <= max){
|
||||
Bit::htobl(pwo, 0);
|
||||
}
|
||||
// if it fits, write an ending zero to indicate end of page
|
||||
if (pwo <= max){Bit::htobl(pwo, 0);}
|
||||
}
|
||||
|
||||
///Reads the list of pushes from a pointer, assumed to end in four zeroes
|
||||
static void readPushList(char * pwo){
|
||||
/// Reads the list of pushes from a pointer, assumed to end in four zeroes
|
||||
static void readPushList(char *pwo){
|
||||
activePushes.clear();
|
||||
pid_t p = Bit::btohl(pwo);
|
||||
HIGH_MSG("Recovering pushes: %" PRIu32, (uint32_t)p);
|
||||
|
@ -121,8 +120,8 @@ namespace Controller{
|
|||
pwo += 4;
|
||||
for (uint8_t i = 0; i < 3; ++i){
|
||||
uint16_t l = Bit::btohs(pwo);
|
||||
push.append(std::string(pwo+2, l));
|
||||
pwo += 2+l;
|
||||
push.append(std::string(pwo + 2, l));
|
||||
pwo += 2 + l;
|
||||
}
|
||||
INFO_MSG("Recovered push: %s", push.toString().c_str());
|
||||
Util::Procs::remember(p);
|
||||
|
@ -135,11 +134,11 @@ namespace Controller{
|
|||
/// Loops, checking every second if any pushes need restarting.
|
||||
void pushCheckLoop(void *np){
|
||||
{
|
||||
IPC::sharedPage pushReadPage("MstPush", 8*1024*1024, false, false);
|
||||
IPC::sharedPage pushReadPage("MstPush", 8 * 1024 * 1024, false, false);
|
||||
if (pushReadPage.mapped){readPushList(pushReadPage.mapped);}
|
||||
}
|
||||
pushListRead = true;
|
||||
IPC::sharedPage pushPage("MstPush", 8*1024*1024, true, false);
|
||||
IPC::sharedPage pushPage("MstPush", 8 * 1024 * 1024, true, false);
|
||||
while (Controller::conf.is_active){
|
||||
// this scope prevents the configMutex from being locked constantly
|
||||
{
|
||||
|
@ -149,7 +148,8 @@ namespace Controller{
|
|||
long long curCount = 0;
|
||||
jsonForEach(Controller::Storage["autopushes"], it){
|
||||
if (it->size() > 3 && (*it)[3u].asInt() < Util::epoch()){
|
||||
INFO_MSG("Deleting autopush from %s to %s because end time passed", (*it)[0u].asStringRef().c_str(), (*it)[1u].asStringRef().c_str());
|
||||
INFO_MSG("Deleting autopush from %s to %s because end time passed",
|
||||
(*it)[0u].asStringRef().c_str(), (*it)[1u].asStringRef().c_str());
|
||||
stopActivePushes((*it)[0u], (*it)[1u]);
|
||||
removePush(*it);
|
||||
break;
|
||||
|
@ -173,7 +173,8 @@ namespace Controller{
|
|||
const std::string &pStr = (*it)[0u].asStringRef();
|
||||
std::set<std::string> activeStreams = Controller::getActiveStreams(pStr);
|
||||
if (activeStreams.size()){
|
||||
for (std::set<std::string>::iterator jt = activeStreams.begin(); jt != activeStreams.end(); ++jt){
|
||||
for (std::set<std::string>::iterator jt = activeStreams.begin();
|
||||
jt != activeStreams.end(); ++jt){
|
||||
std::string streamname = *jt;
|
||||
std::string target = (*it)[1u];
|
||||
if (pStr == streamname || (*pStr.rbegin() == '+' && streamname.substr(0, pStr.size()) == pStr)){
|
||||
|
@ -201,10 +202,10 @@ namespace Controller{
|
|||
}
|
||||
Util::wait(1000); // wait at least a second
|
||||
}
|
||||
//keep the pushPage if we are restarting, so we can restore state from it
|
||||
// keep the pushPage if we are restarting, so we can restore state from it
|
||||
if (Util::Config::is_restarting){
|
||||
pushPage.master = false;
|
||||
//forget about all pushes, so they keep running
|
||||
// forget about all pushes, so they keep running
|
||||
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
|
||||
Util::Procs::forget(it->first);
|
||||
}
|
||||
|
@ -250,7 +251,8 @@ namespace Controller{
|
|||
}
|
||||
long long epo = Util::epoch();
|
||||
if (newPush.size() > 3 && newPush[3u].asInt() <= epo){
|
||||
WARN_MSG("Automatic push not added: removal time is in the past! (%" PRId64 " <= %" PRIu64 ")", newPush[3u].asInt(), Util::epoch());
|
||||
WARN_MSG("Automatic push not added: removal time is in the past! (%" PRId64 " <= %" PRIu64 ")",
|
||||
newPush[3u].asInt(), Util::epoch());
|
||||
return;
|
||||
}
|
||||
bool edited = false;
|
||||
|
@ -333,10 +335,13 @@ namespace Controller{
|
|||
|
||||
void pushSettings(const JSON::Value &request, JSON::Value &response){
|
||||
if (request.isObject()){
|
||||
if (request.isMember("wait")){Controller::Storage["push_settings"]["wait"] = request["wait"].asInt();}
|
||||
if (request.isMember("maxspeed")){Controller::Storage["push_settings"]["maxspeed"] = request["maxspeed"].asInt();}
|
||||
if (request.isMember("wait")){
|
||||
Controller::Storage["push_settings"]["wait"] = request["wait"].asInt();
|
||||
}
|
||||
if (request.isMember("maxspeed")){
|
||||
Controller::Storage["push_settings"]["maxspeed"] = request["maxspeed"].asInt();
|
||||
}
|
||||
}
|
||||
response = Controller::Storage["push_settings"];
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace Controller
|
||||
|
|
|
@ -22,5 +22,4 @@ namespace Controller{
|
|||
|
||||
// for storing/retrieving settings
|
||||
void pushSettings(const JSON::Value &request, JSON::Value &response);
|
||||
}
|
||||
|
||||
}// namespace Controller
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,126 +1,117 @@
|
|||
#pragma once
|
||||
#include <mist/shared_memory.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/tinythread.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/socket.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/shared_memory.h>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/tinythread.h>
|
||||
#include <string>
|
||||
|
||||
/// The STAT_CUTOFF define sets how many seconds of statistics history is kept.
|
||||
#define STAT_CUTOFF 600
|
||||
|
||||
namespace Controller{
|
||||
|
||||
namespace Controller {
|
||||
|
||||
extern bool killOnExit;
|
||||
extern unsigned int maxConnsPerIP;
|
||||
|
||||
///This function is ran whenever a stream becomes active.
|
||||
/// This function is ran whenever a stream becomes active.
|
||||
void streamStarted(std::string stream);
|
||||
///This function is ran whenever a stream becomes inactive.
|
||||
/// This function is ran whenever a stream becomes inactive.
|
||||
void streamStopped(std::string stream);
|
||||
|
||||
|
||||
void updateBandwidthConfig();
|
||||
|
||||
struct statLog {
|
||||
|
||||
struct statLog{
|
||||
uint64_t time;
|
||||
uint64_t lastSecond;
|
||||
uint64_t down;
|
||||
uint64_t up;
|
||||
};
|
||||
|
||||
enum sessType {
|
||||
SESS_UNSET = 0,
|
||||
SESS_INPUT,
|
||||
SESS_OUTPUT,
|
||||
SESS_VIEWER
|
||||
};
|
||||
enum sessType{SESS_UNSET = 0, SESS_INPUT, SESS_OUTPUT, SESS_VIEWER};
|
||||
|
||||
/// This is a comparison and storage class that keeps sessions apart from each other.
|
||||
/// Whenever two of these objects are not equal, it will create a new session.
|
||||
class sessIndex {
|
||||
public:
|
||||
sessIndex(std::string host, unsigned int crc, std::string streamName, std::string connector);
|
||||
sessIndex(IPC::statExchange & data);
|
||||
sessIndex();
|
||||
std::string ID;
|
||||
std::string host;
|
||||
unsigned int crc;
|
||||
std::string streamName;
|
||||
std::string connector;
|
||||
|
||||
bool operator== (const sessIndex &o) const;
|
||||
bool operator!= (const sessIndex &o) const;
|
||||
bool operator> (const sessIndex &o) const;
|
||||
bool operator<= (const sessIndex &o) const;
|
||||
bool operator< (const sessIndex &o) const;
|
||||
bool operator>= (const sessIndex &o) const;
|
||||
std::string toStr();
|
||||
};
|
||||
|
||||
|
||||
class statStorage {
|
||||
public:
|
||||
void update(IPC::statExchange & data);
|
||||
bool hasDataFor(unsigned long long);
|
||||
statLog & getDataFor(unsigned long long);
|
||||
std::map<unsigned long long, statLog> log;
|
||||
};
|
||||
|
||||
/// A session class that keeps track of both current and archived connections.
|
||||
/// Allows for moving of connections to another session.
|
||||
class statSession {
|
||||
private:
|
||||
uint64_t firstActive;
|
||||
uint64_t firstSec;
|
||||
uint64_t lastSec;
|
||||
uint64_t wipedUp;
|
||||
uint64_t wipedDown;
|
||||
std::deque<statStorage> oldConns;
|
||||
sessType sessionType;
|
||||
bool tracked;
|
||||
uint8_t noBWCount;///<Set to 2 when not to count for external bandwidth
|
||||
public:
|
||||
statSession();
|
||||
uint32_t invalidate();
|
||||
uint32_t kill();
|
||||
char sync;
|
||||
std::map<uint64_t, statStorage> curConns;
|
||||
std::set<std::string> tags;
|
||||
sessType getSessType();
|
||||
void wipeOld(uint64_t);
|
||||
void finish(uint64_t index);
|
||||
void switchOverTo(statSession & newSess, uint64_t index);
|
||||
void update(uint64_t index, IPC::statExchange & data);
|
||||
void ping(const sessIndex & index, uint64_t disconnectPoint);
|
||||
uint64_t getStart();
|
||||
uint64_t getEnd();
|
||||
bool isViewerOn(uint64_t time);
|
||||
bool isViewer();
|
||||
bool hasDataFor(uint64_t time);
|
||||
bool hasData();
|
||||
uint64_t getConnTime(uint64_t time);
|
||||
uint64_t getLastSecond(uint64_t time);
|
||||
uint64_t getDown(uint64_t time);
|
||||
uint64_t getUp();
|
||||
uint64_t getDown();
|
||||
uint64_t getUp(uint64_t time);
|
||||
uint64_t getBpsDown(uint64_t time);
|
||||
uint64_t getBpsUp(uint64_t time);
|
||||
uint64_t getBpsDown(uint64_t start, uint64_t end);
|
||||
uint64_t getBpsUp(uint64_t start, uint64_t end);
|
||||
class sessIndex{
|
||||
public:
|
||||
sessIndex(std::string host, unsigned int crc, std::string streamName, std::string connector);
|
||||
sessIndex(IPC::statExchange &data);
|
||||
sessIndex();
|
||||
std::string ID;
|
||||
std::string host;
|
||||
unsigned int crc;
|
||||
std::string streamName;
|
||||
std::string connector;
|
||||
|
||||
bool operator==(const sessIndex &o) const;
|
||||
bool operator!=(const sessIndex &o) const;
|
||||
bool operator>(const sessIndex &o) const;
|
||||
bool operator<=(const sessIndex &o) const;
|
||||
bool operator<(const sessIndex &o) const;
|
||||
bool operator>=(const sessIndex &o) const;
|
||||
std::string toStr();
|
||||
};
|
||||
|
||||
class statStorage{
|
||||
public:
|
||||
void update(IPC::statExchange &data);
|
||||
bool hasDataFor(unsigned long long);
|
||||
statLog &getDataFor(unsigned long long);
|
||||
std::map<unsigned long long, statLog> log;
|
||||
};
|
||||
|
||||
/// A session class that keeps track of both current and archived connections.
|
||||
/// Allows for moving of connections to another session.
|
||||
class statSession{
|
||||
private:
|
||||
uint64_t firstActive;
|
||||
uint64_t firstSec;
|
||||
uint64_t lastSec;
|
||||
uint64_t wipedUp;
|
||||
uint64_t wipedDown;
|
||||
std::deque<statStorage> oldConns;
|
||||
sessType sessionType;
|
||||
bool tracked;
|
||||
uint8_t noBWCount; ///< Set to 2 when not to count for external bandwidth
|
||||
public:
|
||||
statSession();
|
||||
uint32_t invalidate();
|
||||
uint32_t kill();
|
||||
char sync;
|
||||
std::map<uint64_t, statStorage> curConns;
|
||||
std::set<std::string> tags;
|
||||
sessType getSessType();
|
||||
void wipeOld(uint64_t);
|
||||
void finish(uint64_t index);
|
||||
void switchOverTo(statSession &newSess, uint64_t index);
|
||||
void update(uint64_t index, IPC::statExchange &data);
|
||||
void ping(const sessIndex &index, uint64_t disconnectPoint);
|
||||
uint64_t getStart();
|
||||
uint64_t getEnd();
|
||||
bool isViewerOn(uint64_t time);
|
||||
bool isViewer();
|
||||
bool hasDataFor(uint64_t time);
|
||||
bool hasData();
|
||||
uint64_t getConnTime(uint64_t time);
|
||||
uint64_t getLastSecond(uint64_t time);
|
||||
uint64_t getDown(uint64_t time);
|
||||
uint64_t getUp();
|
||||
uint64_t getDown();
|
||||
uint64_t getUp(uint64_t time);
|
||||
uint64_t getBpsDown(uint64_t time);
|
||||
uint64_t getBpsUp(uint64_t time);
|
||||
uint64_t getBpsDown(uint64_t start, uint64_t end);
|
||||
uint64_t getBpsUp(uint64_t start, uint64_t end);
|
||||
};
|
||||
|
||||
|
||||
extern std::map<sessIndex, statSession> sessions;
|
||||
extern std::map<unsigned long, sessIndex> connToSession;
|
||||
extern tthread::mutex statsMutex;
|
||||
|
||||
|
||||
struct triggerLog {
|
||||
struct triggerLog{
|
||||
uint64_t totalCount;
|
||||
uint64_t failCount;
|
||||
uint64_t ms;
|
||||
|
@ -128,24 +119,23 @@ namespace Controller {
|
|||
|
||||
extern std::map<std::string, triggerLog> triggerStats;
|
||||
|
||||
std::set<std::string> getActiveStreams(const std::string & prefix = "");
|
||||
void parseStatistics(char * data, size_t len, unsigned int id);
|
||||
void killStatistics(char * data, size_t len, unsigned int id);
|
||||
void fillClients(JSON::Value & req, JSON::Value & rep);
|
||||
void fillActive(JSON::Value & req, JSON::Value & rep, bool onlyNow = false);
|
||||
void fillTotals(JSON::Value & req, JSON::Value & rep);
|
||||
void SharedMemStats(void * config);
|
||||
void sessions_invalidate(const std::string & streamname);
|
||||
void sessions_shutdown(JSON::Iter & i);
|
||||
void sessId_shutdown(const std::string & sessId);
|
||||
void tag_shutdown(const std::string & tag);
|
||||
void sessId_tag(const std::string & sessId, const std::string & tag);
|
||||
void sessions_shutdown(const std::string & streamname, const std::string & protocol = "");
|
||||
std::set<std::string> getActiveStreams(const std::string &prefix = "");
|
||||
void parseStatistics(char *data, size_t len, unsigned int id);
|
||||
void killStatistics(char *data, size_t len, unsigned int id);
|
||||
void fillClients(JSON::Value &req, JSON::Value &rep);
|
||||
void fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow = false);
|
||||
void fillTotals(JSON::Value &req, JSON::Value &rep);
|
||||
void SharedMemStats(void *config);
|
||||
void sessions_invalidate(const std::string &streamname);
|
||||
void sessions_shutdown(JSON::Iter &i);
|
||||
void sessId_shutdown(const std::string &sessId);
|
||||
void tag_shutdown(const std::string &tag);
|
||||
void sessId_tag(const std::string &sessId, const std::string &tag);
|
||||
void sessions_shutdown(const std::string &streamname, const std::string &protocol = "");
|
||||
bool hasViewers(std::string streamName);
|
||||
void writeSessionCache(); /*LTS*/
|
||||
|
||||
#define PROMETHEUS_TEXT 0
|
||||
#define PROMETHEUS_JSON 1
|
||||
void handlePrometheus(HTTP::Parser & H, Socket::Connection & conn, int mode);
|
||||
}
|
||||
|
||||
void handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int mode);
|
||||
}// namespace Controller
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
#include <sys/stat.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include "controller_capabilities.h"
|
||||
#include "controller_storage.h"
|
||||
#include <algorithm>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/shared_memory.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/shared_memory.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/triggers.h> //LTS
|
||||
#include "controller_storage.h"
|
||||
#include "controller_capabilities.h"
|
||||
#include <sys/stat.h>
|
||||
|
||||
///\brief Holds everything unique to the controller.
|
||||
namespace Controller{
|
||||
|
@ -26,29 +25,23 @@ namespace Controller{
|
|||
uint32_t maxLogsRecs = 0;
|
||||
uint32_t maxAccsRecs = 0;
|
||||
uint64_t firstLog = 0;
|
||||
IPC::sharedPage * shmLogs = 0;
|
||||
Util::RelAccX * rlxLogs = 0;
|
||||
IPC::sharedPage * shmAccs = 0;
|
||||
Util::RelAccX * rlxAccs = 0;
|
||||
IPC::sharedPage * shmStrm = 0;
|
||||
Util::RelAccX * rlxStrm = 0;
|
||||
IPC::sharedPage *shmLogs = 0;
|
||||
Util::RelAccX *rlxLogs = 0;
|
||||
IPC::sharedPage *shmAccs = 0;
|
||||
Util::RelAccX *rlxAccs = 0;
|
||||
IPC::sharedPage *shmStrm = 0;
|
||||
Util::RelAccX *rlxStrm = 0;
|
||||
|
||||
Util::RelAccX * logAccessor(){
|
||||
return rlxLogs;
|
||||
}
|
||||
Util::RelAccX *logAccessor(){return rlxLogs;}
|
||||
|
||||
Util::RelAccX * accesslogAccessor(){
|
||||
return rlxAccs;
|
||||
}
|
||||
Util::RelAccX *accesslogAccessor(){return rlxAccs;}
|
||||
|
||||
Util::RelAccX * streamsAccessor(){
|
||||
return rlxStrm;
|
||||
}
|
||||
Util::RelAccX *streamsAccessor(){return rlxStrm;}
|
||||
|
||||
///\brief Store and print a log message.
|
||||
///\param kind The type of message.
|
||||
///\param message The message to be logged.
|
||||
void Log(const std::string & kind, const std::string & message, const std::string & stream, bool noWriteToLog){
|
||||
void Log(const std::string &kind, const std::string &message, const std::string &stream, bool noWriteToLog){
|
||||
if (noWriteToLog){
|
||||
tthread::lock_guard<tthread::mutex> guard(logMutex);
|
||||
JSON::Value m;
|
||||
|
@ -61,15 +54,13 @@ namespace Controller{
|
|||
Storage["log"].shrink(100); // limit to 100 log messages
|
||||
logCounter++;
|
||||
if (rlxLogs && rlxLogs->isReady()){
|
||||
if (!firstLog){
|
||||
firstLog = logCounter;
|
||||
}
|
||||
if (!firstLog){firstLog = logCounter;}
|
||||
rlxLogs->setRCount(logCounter > maxLogsRecs ? maxLogsRecs : logCounter);
|
||||
rlxLogs->setDeleted(logCounter > rlxLogs->getRCount() ? logCounter - rlxLogs->getRCount() : firstLog);
|
||||
rlxLogs->setInt("time", logTime, logCounter-1);
|
||||
rlxLogs->setString("kind", kind, logCounter-1);
|
||||
rlxLogs->setString("msg", message, logCounter-1);
|
||||
rlxLogs->setString("strm", stream, logCounter-1);
|
||||
rlxLogs->setInt("time", logTime, logCounter - 1);
|
||||
rlxLogs->setString("kind", kind, logCounter - 1);
|
||||
rlxLogs->setString("msg", message, logCounter - 1);
|
||||
rlxLogs->setString("strm", stream, logCounter - 1);
|
||||
rlxLogs->setEndPos(logCounter);
|
||||
}
|
||||
}else{
|
||||
|
@ -77,10 +68,12 @@ namespace Controller{
|
|||
}
|
||||
}
|
||||
|
||||
void logAccess(const std::string & sessId, const std::string & strm, const std::string & conn, const std::string & host, uint64_t duration, uint64_t up, uint64_t down, const std::string & tags){
|
||||
void logAccess(const std::string &sessId, const std::string &strm, const std::string &conn,
|
||||
const std::string &host, uint64_t duration, uint64_t up, uint64_t down,
|
||||
const std::string &tags){
|
||||
if (rlxAccs && rlxAccs->isReady()){
|
||||
uint64_t newEndPos = rlxAccs->getEndPos();
|
||||
rlxAccs->setRCount(newEndPos+1 > maxLogsRecs ? maxAccsRecs : newEndPos+1);
|
||||
rlxAccs->setRCount(newEndPos + 1 > maxLogsRecs ? maxAccsRecs : newEndPos + 1);
|
||||
rlxAccs->setDeleted(newEndPos + 1 > maxAccsRecs ? newEndPos + 1 - maxAccsRecs : 0);
|
||||
rlxAccs->setInt("time", Util::epoch(), newEndPos);
|
||||
rlxAccs->setString("session", sessId, newEndPos);
|
||||
|
@ -95,16 +88,23 @@ namespace Controller{
|
|||
}
|
||||
if (Triggers::shouldTrigger("USER_END", strm)){
|
||||
std::stringstream plgen;
|
||||
plgen << sessId << "\n" << strm << "\n" << conn << "\n" << host << "\n" << duration << "\n" << up << "\n" << down << "\n" << tags;
|
||||
plgen << sessId << "\n"
|
||||
<< strm << "\n"
|
||||
<< conn << "\n"
|
||||
<< host << "\n"
|
||||
<< duration << "\n"
|
||||
<< up << "\n"
|
||||
<< down << "\n"
|
||||
<< tags;
|
||||
std::string payload = plgen.str();
|
||||
Triggers::doTrigger("USER_END", payload, strm);
|
||||
}
|
||||
}
|
||||
|
||||
void normalizeTrustedProxies(JSON::Value & tp){
|
||||
//First normalize to arrays
|
||||
void normalizeTrustedProxies(JSON::Value &tp){
|
||||
// First normalize to arrays
|
||||
if (!tp.isArray()){tp.append(tp.asString());}
|
||||
//Now, wipe any empty entries, and convert spaces to array entries
|
||||
// Now, wipe any empty entries, and convert spaces to array entries
|
||||
std::set<std::string> n;
|
||||
jsonForEach(tp, jit){
|
||||
if (!jit->isString()){*jit = jit->asString();}
|
||||
|
@ -116,16 +116,14 @@ namespace Controller{
|
|||
while (tmp.find(' ') != std::string::npos){
|
||||
size_t p = tmp.find(' ');
|
||||
n.insert(tmp.substr(0, p));
|
||||
tmp.erase(0, p+1);
|
||||
tmp.erase(0, p + 1);
|
||||
}
|
||||
if (tmp.size()){n.insert(tmp);}
|
||||
}
|
||||
n.erase("");
|
||||
//Re-write the entire array, which is now normalized
|
||||
// Re-write the entire array, which is now normalized
|
||||
tp.shrink(0);
|
||||
for (std::set<std::string>::iterator it = n.begin(); it != n.end(); ++it){
|
||||
tp.append(*it);
|
||||
}
|
||||
for (std::set<std::string>::iterator it = n.begin(); it != n.end(); ++it){tp.append(*it);}
|
||||
}
|
||||
|
||||
///\brief Write contents to Filename
|
||||
|
@ -141,7 +139,7 @@ namespace Controller{
|
|||
|
||||
void initState(){
|
||||
tthread::lock_guard<tthread::mutex> guard(logMutex);
|
||||
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024*1024, true);//max 1M of logs cached
|
||||
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, true); // max 1M of logs cached
|
||||
if (!shmLogs->mapped){
|
||||
FAIL_MSG("Could not open memory page for logs buffer");
|
||||
return;
|
||||
|
@ -156,9 +154,9 @@ namespace Controller{
|
|||
rlxLogs->addField("strm", RAX_128STRING);
|
||||
rlxLogs->setReady();
|
||||
}
|
||||
maxLogsRecs = (1024*1024 - rlxLogs->getOffset()) / rlxLogs->getRSize();
|
||||
maxLogsRecs = (1024 * 1024 - rlxLogs->getOffset()) / rlxLogs->getRSize();
|
||||
|
||||
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024*1024, true);//max 1M of accesslogs cached
|
||||
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, true); // max 1M of accesslogs cached
|
||||
if (!shmAccs->mapped){
|
||||
FAIL_MSG("Could not open memory page for access logs buffer");
|
||||
return;
|
||||
|
@ -176,9 +174,9 @@ namespace Controller{
|
|||
rlxAccs->addField("tags", RAX_256STRING);
|
||||
rlxAccs->setReady();
|
||||
}
|
||||
maxAccsRecs = (1024*1024 - rlxAccs->getOffset()) / rlxAccs->getRSize();
|
||||
maxAccsRecs = (1024 * 1024 - rlxAccs->getOffset()) / rlxAccs->getRSize();
|
||||
|
||||
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024*1024, true);//max 1M of stream data
|
||||
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, true); // max 1M of stream data
|
||||
if (!shmStrm->mapped){
|
||||
FAIL_MSG("Could not open memory page for stream data");
|
||||
return;
|
||||
|
@ -192,7 +190,7 @@ namespace Controller{
|
|||
rlxStrm->addField("outputs", RAX_64UINT);
|
||||
rlxStrm->setReady();
|
||||
}
|
||||
rlxStrm->setRCount((1024*1024 - rlxStrm->getOffset()) / rlxStrm->getRSize());
|
||||
rlxStrm->setRCount((1024 * 1024 - rlxStrm->getOffset()) / rlxStrm->getRSize());
|
||||
}
|
||||
|
||||
void deinitState(bool leaveBehind){
|
||||
|
@ -209,7 +207,7 @@ namespace Controller{
|
|||
shmAccs->master = false;
|
||||
shmStrm->master = false;
|
||||
}
|
||||
Util::RelAccX * tmp = rlxLogs;
|
||||
Util::RelAccX *tmp = rlxLogs;
|
||||
rlxLogs = 0;
|
||||
delete tmp;
|
||||
delete shmLogs;
|
||||
|
@ -239,7 +237,7 @@ namespace Controller{
|
|||
skip.insert("online");
|
||||
skip.insert("error");
|
||||
tmp.assignFrom(Controller::Storage, skip);
|
||||
if ( !Controller::WriteFile(Controller::conf.getString("configFile"), tmp.toString())){
|
||||
if (!Controller::WriteFile(Controller::conf.getString("configFile"), tmp.toString())){
|
||||
ERROR_MSG("Error writing config to %s", Controller::conf.getString("configFile").c_str());
|
||||
std::cout << "**Config**" << std::endl;
|
||||
std::cout << tmp.toString() << std::endl;
|
||||
|
@ -247,12 +245,12 @@ namespace Controller{
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void writeCapabilities(){
|
||||
std::string temp = capabilities.toPacked();
|
||||
static IPC::sharedPage mistCapaOut(SHM_CAPA, temp.size()+100, true, false);
|
||||
static IPC::sharedPage mistCapaOut(SHM_CAPA, temp.size() + 100, true, false);
|
||||
if (!mistCapaOut.mapped){
|
||||
FAIL_MSG("Could not open capabilities config for writing! Is shared memory enabled on your system?");
|
||||
FAIL_MSG("Could not open capabilities config for writing! Is shared memory enabled on your "
|
||||
"system?");
|
||||
return;
|
||||
}
|
||||
Util::RelAccX A(mistCapaOut.mapped, false);
|
||||
|
@ -270,7 +268,7 @@ namespace Controller{
|
|||
if (Storage["config"]["trustedproxy"].isArray()){
|
||||
jsonForEachConst(Storage["config"]["trustedproxy"], jit){
|
||||
if (tmpProxy.size()){
|
||||
tmpProxy += " "+jit->asString();
|
||||
tmpProxy += " " + jit->asString();
|
||||
}else{
|
||||
tmpProxy = jit->asString();
|
||||
}
|
||||
|
@ -280,11 +278,12 @@ namespace Controller{
|
|||
}
|
||||
if (proxy_written != tmpProxy){
|
||||
proxy_written = tmpProxy;
|
||||
static IPC::sharedPage mistProxOut(SHM_PROXY, proxy_written.size()+100, true, false);
|
||||
static IPC::sharedPage mistProxOut(SHM_PROXY, proxy_written.size() + 100, true, false);
|
||||
mistProxOut.close();
|
||||
mistProxOut.init(SHM_PROXY, proxy_written.size()+100, true, false);
|
||||
mistProxOut.init(SHM_PROXY, proxy_written.size() + 100, true, false);
|
||||
if (!mistProxOut.mapped){
|
||||
FAIL_MSG("Could not open trusted proxy config for writing! Is shared memory enabled on your system?");
|
||||
FAIL_MSG("Could not open trusted proxy config for writing! Is shared memory enabled on "
|
||||
"your system?");
|
||||
return;
|
||||
}else{
|
||||
Util::RelAccX A(mistProxOut.mapped, false);
|
||||
|
@ -303,11 +302,12 @@ namespace Controller{
|
|||
if (Storage["config"]["protocols"].compareExcept(proto_written, skip)){return;}
|
||||
proto_written.assignFrom(Storage["config"]["protocols"], skip);
|
||||
std::string temp = proto_written.toPacked();
|
||||
static IPC::sharedPage mistProtoOut(SHM_PROTO, temp.size()+100, true, false);
|
||||
static IPC::sharedPage mistProtoOut(SHM_PROTO, temp.size() + 100, true, false);
|
||||
mistProtoOut.close();
|
||||
mistProtoOut.init(SHM_PROTO, temp.size()+100, true, false);
|
||||
mistProtoOut.init(SHM_PROTO, temp.size() + 100, true, false);
|
||||
if (!mistProtoOut.mapped){
|
||||
FAIL_MSG("Could not open protocol config for writing! Is shared memory enabled on your system?");
|
||||
FAIL_MSG(
|
||||
"Could not open protocol config for writing! Is shared memory enabled on your system?");
|
||||
return;
|
||||
}
|
||||
// write config
|
||||
|
@ -322,7 +322,7 @@ namespace Controller{
|
|||
}
|
||||
}
|
||||
|
||||
void writeStream(const std::string & sName, const JSON::Value & sConf){
|
||||
void writeStream(const std::string &sName, const JSON::Value &sConf){
|
||||
static std::map<std::string, JSON::Value> writtenStrms;
|
||||
static std::map<std::string, IPC::sharedPage> pages;
|
||||
static std::set<std::string> skip;
|
||||
|
@ -338,12 +338,12 @@ namespace Controller{
|
|||
}
|
||||
if (!writtenStrms.count(sName) || !writtenStrms[sName].compareExcept(sConf, skip)){
|
||||
writtenStrms[sName].assignFrom(sConf, skip);
|
||||
IPC::sharedPage & P = pages[sName];
|
||||
IPC::sharedPage &P = pages[sName];
|
||||
std::string temp = writtenStrms[sName].toPacked();
|
||||
P.close();
|
||||
char tmpBuf[NAME_BUFFER_SIZE];
|
||||
snprintf(tmpBuf, NAME_BUFFER_SIZE, SHM_STREAM_CONF, sName.c_str());
|
||||
P.init(tmpBuf, temp.size()+100, true, false);
|
||||
P.init(tmpBuf, temp.size() + 100, true, false);
|
||||
if (!P){
|
||||
writtenStrms.erase(sName);
|
||||
pages.erase(sName);
|
||||
|
@ -374,12 +374,10 @@ namespace Controller{
|
|||
}
|
||||
|
||||
{
|
||||
//Global configuration options, if any
|
||||
// Global configuration options, if any
|
||||
IPC::sharedPage globCfg;
|
||||
globCfg.init(SHM_GLOBAL_CONF, 4096, false, false);
|
||||
if (!globCfg.mapped){
|
||||
globCfg.init(SHM_GLOBAL_CONF, 4096, true, false);
|
||||
}
|
||||
if (!globCfg.mapped){globCfg.init(SHM_GLOBAL_CONF, 4096, true, false);}
|
||||
if (globCfg.mapped){
|
||||
Util::RelAccX globAccX(globCfg.mapped, false);
|
||||
if (!globAccX.isReady()){
|
||||
|
@ -389,11 +387,10 @@ namespace Controller{
|
|||
globAccX.setReady();
|
||||
}
|
||||
globAccX.setString("defaultStream", Storage["config"]["defaultStream"].asStringRef());
|
||||
globCfg.master = false;//leave the page after closing
|
||||
globCfg.master = false; // leave the page after closing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*LTS-START*/
|
||||
static std::map<std::string, IPC::sharedPage> pageForType; // should contain one page for every trigger type
|
||||
static JSON::Value writtenTrigs;
|
||||
|
@ -438,7 +435,9 @@ namespace Controller{
|
|||
namesArray.append(tmpBuf, 4);
|
||||
namesArray.append(shIt->asString());
|
||||
}
|
||||
if (namesArray.size()){memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));}
|
||||
if (namesArray.size()){
|
||||
memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -457,7 +456,9 @@ namespace Controller{
|
|||
namesArray.append(tmpBuf, 4);
|
||||
namesArray.append(shIt->asString());
|
||||
}
|
||||
if (namesArray.size()){memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));}
|
||||
if (namesArray.size()){
|
||||
memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (triggIt->isMember("params") && !(*triggIt)["params"].isNull()){
|
||||
|
@ -492,5 +493,4 @@ namespace Controller{
|
|||
}
|
||||
/*LTS-END*/
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace Controller
|
||||
|
|
|
@ -1,44 +1,45 @@
|
|||
#include <string>
|
||||
#include <mist/json.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/tinythread.h>
|
||||
#include <mist/util.h>
|
||||
#include <string>
|
||||
|
||||
namespace Controller {
|
||||
extern std::string instanceId; ///<global storage of instanceId (previously uniqID) is set in controller.cpp
|
||||
extern std::string prometheus; ///< Prometheus access string
|
||||
extern std::string accesslog; ///< Where to write the access log
|
||||
extern Util::Config conf;///< Global storage of configuration.
|
||||
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
|
||||
|
||||
Util::RelAccX * logAccessor();
|
||||
Util::RelAccX * accesslogAccessor();
|
||||
Util::RelAccX * streamsAccessor();
|
||||
namespace Controller{
|
||||
extern std::string instanceId; ///< global storage of instanceId (previously uniqID) is set in controller.cpp
|
||||
extern std::string prometheus; ///< Prometheus access string
|
||||
extern std::string accesslog; ///< Where to write the access log
|
||||
extern Util::Config conf; ///< Global storage of configuration.
|
||||
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
|
||||
|
||||
Util::RelAccX *logAccessor();
|
||||
Util::RelAccX *accesslogAccessor();
|
||||
Util::RelAccX *streamsAccessor();
|
||||
|
||||
/// Store and print a log message.
|
||||
void Log(const std::string & kind, const std::string & message, const std::string & stream = "", bool noWriteToLog = false);
|
||||
void logAccess(const std::string & sessId, const std::string & strm, const std::string & conn, const std::string & host, uint64_t duration, uint64_t up, uint64_t down, const std::string & tags);
|
||||
|
||||
|
||||
void normalizeTrustedProxies(JSON::Value & tp);
|
||||
void Log(const std::string &kind, const std::string &message, const std::string &stream = "",
|
||||
bool noWriteToLog = false);
|
||||
void logAccess(const std::string &sessId, const std::string &strm, const std::string &conn,
|
||||
const std::string &host, uint64_t duration, uint64_t up, uint64_t down,
|
||||
const std::string &tags);
|
||||
|
||||
void normalizeTrustedProxies(JSON::Value &tp);
|
||||
|
||||
/// Write contents to Filename.
|
||||
bool WriteFile(std::string Filename, std::string contents);
|
||||
void writeConfigToDisk();
|
||||
|
||||
void handleMsg(void * err);
|
||||
|
||||
void handleMsg(void *err);
|
||||
void initState();
|
||||
void deinitState(bool leaveBehind);
|
||||
void writeConfig();
|
||||
void writeStream(const std::string & sName, const JSON::Value & sConf);
|
||||
void writeStream(const std::string &sName, const JSON::Value &sConf);
|
||||
void writeCapabilities();
|
||||
void writeProtocols();
|
||||
|
||||
}
|
||||
}// namespace Controller
|
||||
|
|
|
@ -1,87 +1,83 @@
|
|||
#include <mist/procs.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/shared_memory.h>
|
||||
#include "controller_streams.h"
|
||||
#include "controller_capabilities.h"
|
||||
#include "controller_storage.h"
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_limits.h" /*LTS*/
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_storage.h"
|
||||
#include "controller_streams.h"
|
||||
#include <map>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/procs.h>
|
||||
#include <mist/shared_memory.h>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/triggers.h> //LTS
|
||||
#include <sys/stat.h>
|
||||
#include <map>
|
||||
|
||||
///\brief Holds everything unique to the controller.
|
||||
namespace Controller {
|
||||
namespace Controller{
|
||||
std::map<std::string, pid_t> inputProcesses;
|
||||
|
||||
///\brief Checks whether two streams are equal.
|
||||
///\param one The first stream for the comparison.
|
||||
///\param two The second stream for the comparison.
|
||||
///\return True if the streams are equal, false otherwise.
|
||||
bool streamsEqual(JSON::Value & one, JSON::Value & two){
|
||||
bool streamsEqual(JSON::Value &one, JSON::Value &two){
|
||||
if (one.isMember("source") != two.isMember("source") || one["source"] != two["source"]){
|
||||
return false;
|
||||
}
|
||||
|
||||
/// \todo Change this to use capabilities["inputs"] and only compare required/optional parameters.
|
||||
/// \todo Maybe change this to check for correct source and/or required parameters.
|
||||
|
||||
//temporary: compare the two JSON::Value objects.
|
||||
return one==two;
|
||||
|
||||
//nothing different? return true by default
|
||||
//return true;
|
||||
|
||||
/// \todo Change this to use capabilities["inputs"] and only compare required/optional
|
||||
/// parameters. \todo Maybe change this to check for correct source and/or required parameters.
|
||||
|
||||
// temporary: compare the two JSON::Value objects.
|
||||
return one == two;
|
||||
|
||||
// nothing different? return true by default
|
||||
// return true;
|
||||
}
|
||||
|
||||
///\brief Checks the validity of a stream, updates internal stream status.
|
||||
///\param name The name of the stream
|
||||
///\param data The corresponding configuration values.
|
||||
void checkStream(std::string name, JSON::Value & data){
|
||||
void checkStream(std::string name, JSON::Value &data){
|
||||
if (!data.isMember("name")){data["name"] = name;}
|
||||
std::string prevState = data["error"].asStringRef();
|
||||
data["online"] = (std::string)"Checking...";
|
||||
data["online"] = (std::string) "Checking...";
|
||||
data.removeMember("error");
|
||||
data.removeNullMembers();
|
||||
switch (Util::getStreamStatus(name)){
|
||||
case STRMSTAT_OFF:
|
||||
//Do nothing
|
||||
break;
|
||||
case STRMSTAT_INIT:
|
||||
data["online"] = 2;
|
||||
data["error"] = "Initializing...";
|
||||
return;
|
||||
case STRMSTAT_BOOT:
|
||||
data["online"] = 2;
|
||||
data["error"] = "Loading...";
|
||||
return;
|
||||
case STRMSTAT_WAIT:
|
||||
data["online"] = 2;
|
||||
data["error"] = "Waiting for data...";
|
||||
return;
|
||||
case STRMSTAT_READY:
|
||||
data["online"] = 1;
|
||||
return;
|
||||
case STRMSTAT_SHUTDOWN:
|
||||
data["online"] = 2;
|
||||
data["error"] = "Shutting down...";
|
||||
return;
|
||||
default:
|
||||
//Unknown state?
|
||||
data["error"] = "Unrecognized stream state";
|
||||
break;
|
||||
case STRMSTAT_OFF:
|
||||
// Do nothing
|
||||
break;
|
||||
case STRMSTAT_INIT:
|
||||
data["online"] = 2;
|
||||
data["error"] = "Initializing...";
|
||||
return;
|
||||
case STRMSTAT_BOOT:
|
||||
data["online"] = 2;
|
||||
data["error"] = "Loading...";
|
||||
return;
|
||||
case STRMSTAT_WAIT:
|
||||
data["online"] = 2;
|
||||
data["error"] = "Waiting for data...";
|
||||
return;
|
||||
case STRMSTAT_READY: data["online"] = 1; return;
|
||||
case STRMSTAT_SHUTDOWN:
|
||||
data["online"] = 2;
|
||||
data["error"] = "Shutting down...";
|
||||
return;
|
||||
default:
|
||||
// Unknown state?
|
||||
data["error"] = "Unrecognized stream state";
|
||||
break;
|
||||
}
|
||||
data["online"] = 0;
|
||||
std::string URL;
|
||||
if (data.isMember("channel") && data["channel"].isMember("URL")){
|
||||
URL = data["channel"]["URL"].asString();
|
||||
}
|
||||
if (data.isMember("source")){
|
||||
URL = data["source"].asString();
|
||||
}
|
||||
if (data.isMember("source")){URL = data["source"].asString();}
|
||||
if (!URL.size()){
|
||||
data["error"] = "Stream offline: Missing source parameter!";
|
||||
if (data["error"].asStringRef() != prevState){
|
||||
|
@ -89,14 +85,13 @@ namespace Controller {
|
|||
}
|
||||
return;
|
||||
}
|
||||
//Old style always on
|
||||
if (data.isMember("udpport") && data["udpport"].asStringRef().size() && (!inputProcesses.count(name) || !Util::Procs::isRunning(inputProcesses[name]))){
|
||||
const std::string & udpPort = data["udpport"].asStringRef();
|
||||
const std::string & multicast = data["multicastinterface"].asStringRef();
|
||||
URL = "tsudp://"+udpPort;
|
||||
if (multicast.size()){
|
||||
URL.append("/"+multicast);
|
||||
}
|
||||
// Old style always on
|
||||
if (data.isMember("udpport") && data["udpport"].asStringRef().size() &&
|
||||
(!inputProcesses.count(name) || !Util::Procs::isRunning(inputProcesses[name]))){
|
||||
const std::string &udpPort = data["udpport"].asStringRef();
|
||||
const std::string &multicast = data["multicastinterface"].asStringRef();
|
||||
URL = "tsudp://" + udpPort;
|
||||
if (multicast.size()){URL.append("/" + multicast);}
|
||||
// False: start TS input
|
||||
INFO_MSG("No TS input for stream %s, starting it: %s", name.c_str(), URL.c_str());
|
||||
std::deque<std::string> command;
|
||||
|
@ -108,35 +103,29 @@ namespace Controller {
|
|||
int stdOut = 1;
|
||||
int stdErr = 2;
|
||||
pid_t program = Util::Procs::StartPiped(command, &stdIn, &stdOut, &stdErr);
|
||||
if (program){
|
||||
inputProcesses[name] = program;
|
||||
}
|
||||
if (program){inputProcesses[name] = program;}
|
||||
}
|
||||
//new style always on
|
||||
// new style always on
|
||||
if (data.isMember("always_on") && data["always_on"].asBool()){
|
||||
INFO_MSG("Starting always-on input %s: %s", name.c_str(), URL.c_str());
|
||||
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;
|
||||
}
|
||||
if (program){inputProcesses[name] = program;}
|
||||
}
|
||||
//non-VoD stream
|
||||
// non-VoD stream
|
||||
if (URL.substr(0, 1) != "/"){return;}
|
||||
Util::streamVariables(URL, name, "");
|
||||
//VoD-style stream
|
||||
// VoD-style stream
|
||||
struct stat fileinfo;
|
||||
if (stat(URL.c_str(), &fileinfo) != 0){
|
||||
if (stat(URL.c_str(), &fileinfo) != 0){
|
||||
data["error"] = "Stream offline: Not found: " + URL;
|
||||
if (data["error"].asStringRef() != prevState){
|
||||
Log("BUFF", "Warning for VoD stream " + name + "! File not found: " + URL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( !data.isMember("error")){
|
||||
data["error"] = "Available";
|
||||
}
|
||||
if (!data.isMember("error")){data["error"] = "Available";}
|
||||
data["online"] = 2;
|
||||
return;
|
||||
}
|
||||
|
@ -144,12 +133,10 @@ namespace Controller {
|
|||
///\brief Checks all streams, restoring if needed.
|
||||
///\param data The stream configuration for the server.
|
||||
///\returns True if the server status changed
|
||||
bool CheckAllStreams(JSON::Value & data){
|
||||
jsonForEach(data, jit) {
|
||||
checkStream(jit.key(), (*jit));
|
||||
}
|
||||
bool CheckAllStreams(JSON::Value &data){
|
||||
jsonForEach(data, jit){checkStream(jit.key(), (*jit));}
|
||||
|
||||
//check for changes in config or streams
|
||||
// check for changes in config or streams
|
||||
static JSON::Value strlist;
|
||||
if (strlist["config"] != Storage["config"] || strlist["streams"] != Storage["streams"]){
|
||||
strlist["config"] = Storage["config"];
|
||||
|
@ -158,31 +145,31 @@ namespace Controller {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// \triggers
|
||||
/// The `"STREAM_ADD"` trigger is stream-specific, and is ran whenever a new stream is added to the server configuration. If cancelled, the stream is not added. Its payload is:
|
||||
/// \triggers
|
||||
/// The `"STREAM_ADD"` trigger is stream-specific, and is ran whenever a new stream is added to
|
||||
/// the server configuration. If cancelled, the stream is not added. Its payload is:
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// streamname
|
||||
/// configuration in JSON format
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// The `"STREAM_CONFIG"` trigger is stream-specific, and is ran whenever a stream's configuration is changed. If cancelled, the configuration is not changed. Its payload is:
|
||||
/// The `"STREAM_CONFIG"` trigger is stream-specific, and is ran whenever a stream's configuration
|
||||
/// is changed. If cancelled, the configuration is not changed. Its payload is:
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// streamname
|
||||
/// configuration in JSON format
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
///
|
||||
void AddStreams(JSON::Value & in, JSON::Value & out){
|
||||
//check for new streams and updates
|
||||
jsonForEach(in, jit) {
|
||||
///
|
||||
void AddStreams(JSON::Value &in, JSON::Value &out){
|
||||
// check for new streams and updates
|
||||
jsonForEach(in, jit){
|
||||
if (out.isMember(jit.key())){
|
||||
if ( !streamsEqual((*jit), out[jit.key()])){
|
||||
if (!streamsEqual((*jit), out[jit.key()])){
|
||||
/*LTS-START*/
|
||||
if(Triggers::shouldTrigger("STREAM_CONFIG")){
|
||||
std::string payload = jit.key()+"\n"+jit->toString();
|
||||
if (!Triggers::doTrigger("STREAM_CONFIG", payload, jit.key())){
|
||||
continue;
|
||||
}
|
||||
if (Triggers::shouldTrigger("STREAM_CONFIG")){
|
||||
std::string payload = jit.key() + "\n" + jit->toString();
|
||||
if (!Triggers::doTrigger("STREAM_CONFIG", payload, jit.key())){continue;}
|
||||
}
|
||||
/*LTS-END*/
|
||||
out[jit.key()] = (*jit);
|
||||
|
@ -198,16 +185,15 @@ namespace Controller {
|
|||
if (!checked.size()){
|
||||
FAIL_MSG("Invalid stream name '%s'", jit.key().c_str());
|
||||
}else{
|
||||
FAIL_MSG("Invalid stream name '%s'. Suggested alternative: '%s'", jit.key().c_str(), checked.c_str());
|
||||
FAIL_MSG("Invalid stream name '%s'. Suggested alternative: '%s'", jit.key().c_str(),
|
||||
checked.c_str());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/*LTS-START*/
|
||||
if(Triggers::shouldTrigger("STREAM_ADD")){
|
||||
std::string payload = jit.key()+"\n"+jit->toString();
|
||||
if (!Triggers::doTrigger("STREAM_ADD", payload, jit.key())){
|
||||
continue;
|
||||
}
|
||||
if (Triggers::shouldTrigger("STREAM_ADD")){
|
||||
std::string payload = jit.key() + "\n" + jit->toString();
|
||||
if (!Triggers::doTrigger("STREAM_ADD", payload, jit.key())){continue;}
|
||||
}
|
||||
/*LTS-END*/
|
||||
out[jit.key()] = (*jit);
|
||||
|
@ -227,25 +213,28 @@ namespace Controller {
|
|||
/// \api
|
||||
/// `"streams"` requests take the form of:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
/// "streamname_here": { //name of the stream
|
||||
/// "source": "/mnt/media/a.dtsc" //full path to a VoD file, or "push://" followed by the IP or hostname of the machine allowed to push live data. Empty means everyone is allowed to push live data.
|
||||
/// "DVR": 30000 //optional. For live streams, indicates the requested minimum size of the available DVR buffer in milliseconds.
|
||||
/// },
|
||||
///{
|
||||
/// "streamname_here":{//name of the stream
|
||||
/// "source": "/mnt/media/a.dtsc" //full path to a VoD file, or "push://" followed by the IP
|
||||
/// or hostname of the machine allowed to push live data. Empty means everyone is allowed to
|
||||
/// push live data. "DVR": 30000 //optional. For live streams, indicates the requested minimum
|
||||
/// size of the available DVR buffer in milliseconds.
|
||||
///},
|
||||
/// //the above structure repeated for all configured streams
|
||||
/// }
|
||||
///}
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// and are responded to as:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
/// "streamname_here": { //name of the configured stream
|
||||
/// "error": "Available", //error state, if any. "Available" is a special value for VoD streams, indicating it has no current viewers (is not active), but is available for activation.
|
||||
/// "h_meta": 1398113185, //unix time the stream header (if any) was last processed for metadata
|
||||
/// "l_meta": 1398115447, //unix time the stream itself was last processed for metadata
|
||||
/// "meta": { //available metadata for this stream, if any
|
||||
///{
|
||||
/// "streamname_here":{//name of the configured stream
|
||||
/// "error": "Available", //error state, if any. "Available" is a special value for VoD
|
||||
/// streams, indicating it has no current viewers (is not active), but is available for
|
||||
/// activation. "h_meta": 1398113185, //unix time the stream header (if any) was last
|
||||
/// processed for metadata "l_meta": 1398115447, //unix time the stream itself was last
|
||||
/// processed for metadata "meta":{//available metadata for this stream, if any
|
||||
/// "format": "dtsc", //detected media source format
|
||||
/// "tracks": { //list of tracks in this stream
|
||||
/// "audio_AAC_2ch_48000hz_2": {//human-readable track name
|
||||
/// "tracks":{//list of tracks in this stream
|
||||
/// "audio_AAC_2ch_48000hz_2":{//human-readable track name
|
||||
/// "bps": 16043,
|
||||
/// "channels": 2,
|
||||
/// "codec": "AAC",
|
||||
|
@ -256,54 +245,50 @@ namespace Controller {
|
|||
/// "size": 16,
|
||||
/// "trackid": 2,
|
||||
/// "type": "audio"
|
||||
/// },
|
||||
///},
|
||||
/// //the above structure repeated for all tracks
|
||||
/// },
|
||||
///},
|
||||
/// "vod": 1 //indicates VoD stream, or "live" to indicated live stream.
|
||||
/// },
|
||||
///},
|
||||
/// "name": "a", //the stream name, guaranteed to be equal to the object name.
|
||||
/// "online": 2, //online state. 0 = error, 1 = active, 2 = inactive.
|
||||
/// "source": "/home/thulinma/a.dtsc" //source for this stream, as configured.
|
||||
/// },
|
||||
///},
|
||||
/// //the above structure repeated for all configured streams
|
||||
/// }
|
||||
///}
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// Through this request, ALL streams must always be configured. To remove a stream, simply leave it out of the request. To add a stream, simply add it to the request. To edit a stream, simply edit it in the request. The LTS edition has additional requests that allow per-stream changing of the configuration.
|
||||
void CheckStreams(JSON::Value & in, JSON::Value & out){
|
||||
//check for new streams and updates
|
||||
/// Through this request, ALL streams must always be configured. To remove a stream, simply leave
|
||||
/// it out of the request. To add a stream, simply add it to the request. To edit a stream, simply
|
||||
/// edit it in the request. The LTS edition has additional requests that allow per-stream changing
|
||||
/// of the configuration.
|
||||
void CheckStreams(JSON::Value &in, JSON::Value &out){
|
||||
// check for new streams and updates
|
||||
AddStreams(in, out);
|
||||
|
||||
//check for deleted streams
|
||||
// check for deleted streams
|
||||
std::set<std::string> toDelete;
|
||||
jsonForEach(out, jit) {
|
||||
if ( !in.isMember(jit.key())){
|
||||
toDelete.insert(jit.key());
|
||||
}
|
||||
jsonForEach(out, jit){
|
||||
if (!in.isMember(jit.key())){toDelete.insert(jit.key());}
|
||||
}
|
||||
//actually delete the streams
|
||||
// actually delete the streams
|
||||
while (toDelete.size() > 0){
|
||||
std::string deleting = *(toDelete.begin());
|
||||
deleteStream(deleting, out);
|
||||
toDelete.erase(deleting);
|
||||
}
|
||||
|
||||
//update old-style configurations to new-style
|
||||
jsonForEach(in, jit) {
|
||||
// update old-style configurations to new-style
|
||||
jsonForEach(in, jit){
|
||||
if (jit->isMember("channel")){
|
||||
if ( !jit->isMember("source")){
|
||||
(*jit)["source"] = (*jit)["channel"]["URL"];
|
||||
}
|
||||
if (!jit->isMember("source")){(*jit)["source"] = (*jit)["channel"]["URL"];}
|
||||
jit->removeMember("channel");
|
||||
}
|
||||
if (jit->isMember("preset")){
|
||||
jit->removeMember("preset");
|
||||
}
|
||||
if (jit->isMember("preset")){jit->removeMember("preset");}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// 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 deleteStream(const std::string &name, JSON::Value &out, bool sourceFileToo){
|
||||
int ret = 0;
|
||||
if (sourceFileToo){
|
||||
std::string cleaned = name;
|
||||
|
@ -311,9 +296,7 @@ namespace Controller {
|
|||
std::string strmSource;
|
||||
if (Util::getStreamStatus(cleaned) != STRMSTAT_OFF){
|
||||
DTSC::Meta mData = Util::getStreamMeta(cleaned);
|
||||
if (mData.sourceURI.size()){
|
||||
strmSource = mData.sourceURI;
|
||||
}
|
||||
if (mData.sourceURI.size()){strmSource = mData.sourceURI;}
|
||||
}
|
||||
if (!strmSource.size()){
|
||||
std::string smp = cleaned.substr(0, cleaned.find_first_of("+ "));
|
||||
|
@ -338,60 +321,57 @@ namespace Controller {
|
|||
}
|
||||
}
|
||||
if (noFile){
|
||||
WARN_MSG("Not deleting source for stream %s, since the stream does not have an unambiguous source file.", cleaned.c_str());
|
||||
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());
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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 ret;
|
||||
}
|
||||
if (!out.isMember(name)){return ret;}
|
||||
/*LTS-START*/
|
||||
if(Triggers::shouldTrigger("STREAM_REMOVE")){
|
||||
if (!Triggers::doTrigger("STREAM_REMOVE", name, name)){
|
||||
return ret;
|
||||
}
|
||||
if (Triggers::shouldTrigger("STREAM_REMOVE")){
|
||||
if (!Triggers::doTrigger("STREAM_REMOVE", name, name)){return ret;}
|
||||
}
|
||||
/*LTS-END*/
|
||||
Log("STRM", "Deleted stream " + name);
|
||||
out.removeMember(name);
|
||||
Controller::writeStream(name, JSON::Value());//Null JSON value = delete
|
||||
Controller::writeStream(name, JSON::Value()); // Null JSON value = delete
|
||||
++ret;
|
||||
ret *= -1;
|
||||
if (inputProcesses.count(name)){
|
||||
pid_t procId = inputProcesses[name];
|
||||
if (Util::Procs::isRunning(procId)){
|
||||
Util::Procs::Stop(procId);
|
||||
}
|
||||
if (Util::Procs::isRunning(procId)){Util::Procs::Stop(procId);}
|
||||
inputProcesses.erase(name);
|
||||
}
|
||||
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);
|
||||
//if the length of the source is smaller than the front and back matching parts together, it can never match
|
||||
if (source.size() < front.size()+back.size()){return false;}
|
||||
return (source.substr(0,front.size()) == front && source.substr(source.size()-back.size()) == back);
|
||||
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);
|
||||
// if the length of the source is smaller than the front and back matching parts together, it can never match
|
||||
if (source.size() < front.size() + back.size()){return false;}
|
||||
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"];
|
||||
void checkParameters(JSON::Value &streamObj){
|
||||
JSON::Value &inpt = Controller::capabilities["inputs"];
|
||||
std::string match;
|
||||
jsonForEach(inpt, it){
|
||||
if ((*it)["source_match"].isArray()){
|
||||
|
@ -408,11 +388,8 @@ namespace Controller {
|
|||
}
|
||||
}
|
||||
if (match != ""){
|
||||
jsonForEach(inpt[match]["hardcoded"], it){
|
||||
streamObj[it.key()] = *it;
|
||||
}
|
||||
jsonForEach(inpt[match]["hardcoded"], it){streamObj[it.key()] = *it;}
|
||||
}
|
||||
}
|
||||
|
||||
} //Controller namespace
|
||||
|
||||
}// namespace Controller
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
#include <mist/json.h>
|
||||
|
||||
namespace Controller {
|
||||
bool streamsEqual(JSON::Value & one, JSON::Value & two);
|
||||
void checkStream(std::string name, JSON::Value & data);
|
||||
bool CheckAllStreams(JSON::Value & data);
|
||||
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);
|
||||
namespace Controller{
|
||||
bool streamsEqual(JSON::Value &one, JSON::Value &two);
|
||||
void checkStream(std::string name, JSON::Value &data);
|
||||
bool CheckAllStreams(JSON::Value &data);
|
||||
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 {
|
||||
struct liveCheck{
|
||||
long long int lastms;
|
||||
long long int last_active;
|
||||
};
|
||||
|
||||
} //Controller namespace
|
||||
}// namespace Controller
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
/// \file controller_updater.cpp
|
||||
/// Contains all code for the controller updater.
|
||||
|
||||
#include "controller_updater.h"
|
||||
#include "controller_connectors.h"
|
||||
#include "controller_storage.h"
|
||||
#include "controller_updater.h"
|
||||
#include <fstream> //for files
|
||||
#include <iostream> //for stdio
|
||||
#include <mist/auth.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/downloader.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/encode.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/procs.h>
|
||||
#include <mist/timing.h>
|
||||
#include <signal.h> //for raise
|
||||
#include <sys/stat.h> //for chmod
|
||||
#include <time.h> //for time
|
||||
|
@ -66,8 +66,7 @@ namespace Controller{
|
|||
if (Util::epoch() - updateChecker > UPDATE_INTERVAL || updatePerc){
|
||||
JSON::Value result = Controller::checkUpdateInfo();
|
||||
if (result.isMember("error")){
|
||||
FAIL_MSG("Error retrieving update information: %s",
|
||||
result["error"].asStringRef().c_str());
|
||||
FAIL_MSG("Error retrieving update information: %s", result["error"].asStringRef().c_str());
|
||||
}
|
||||
{// Lock the mutex, update the updates object
|
||||
tthread::lock_guard<tthread::mutex> guard(updaterMutex);
|
||||
|
@ -75,7 +74,8 @@ namespace Controller{
|
|||
}
|
||||
if (!result["uptodate"] && updatePerc){
|
||||
if (result["url"].asStringRef().find(".zip") != std::string::npos){
|
||||
FAIL_MSG("Cannot auto-install update for this platform. Please download and install by hand.");
|
||||
FAIL_MSG("Cannot auto-install update for this platform. Please download and install by "
|
||||
"hand.");
|
||||
updatePerc = 0;
|
||||
continue;
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ namespace Controller{
|
|||
#else
|
||||
HTTP::URL url("http://releases.mistserver.org/update.php");
|
||||
#endif
|
||||
DL.dataTimeout = 50;//only timeout if no data received for 50 seconds
|
||||
DL.dataTimeout = 50; // only timeout if no data received for 50 seconds
|
||||
DL.progressCallback = updaterProgressCallback;
|
||||
if (!DL.get(url.link(result["url"].asStringRef())) || !DL.isOk() || !DL.data().size()){
|
||||
FAIL_MSG("Download failed - aborting update");
|
||||
|
@ -94,13 +94,13 @@ namespace Controller{
|
|||
continue;
|
||||
}
|
||||
updatePerc = 50;
|
||||
INFO_MSG("Downloaded update archive of %zuKiB", DL.data().size()/1024);
|
||||
INFO_MSG("Downloaded update archive of %zuKiB", DL.data().size() / 1024);
|
||||
Log("UPDR", "Installing update...");
|
||||
std::string tmpDir = Util::getMyPath();
|
||||
char * tarArgs[4];
|
||||
tarArgs[0] = (char*)"tar";
|
||||
tarArgs[1] = (char*)"-xzC";
|
||||
tarArgs[2] = (char*)tmpDir.c_str();
|
||||
char *tarArgs[4];
|
||||
tarArgs[0] = (char *)"tar";
|
||||
tarArgs[1] = (char *)"-xzC";
|
||||
tarArgs[2] = (char *)tmpDir.c_str();
|
||||
tarArgs[3] = 0;
|
||||
int tarIn = -1;
|
||||
pid_t tarPid = Util::Procs::StartPiped(tarArgs, &tarIn, 0, 0);
|
||||
|
@ -111,13 +111,14 @@ namespace Controller{
|
|||
}
|
||||
size_t tarProgress = 0;
|
||||
while (tarProgress < DL.data().size()){
|
||||
int written = write(tarIn, DL.data().data()+tarProgress, std::min((size_t)4096, DL.data().size() - tarProgress));
|
||||
int written = write(tarIn, DL.data().data() + tarProgress,
|
||||
std::min((size_t)4096, DL.data().size() - tarProgress));
|
||||
if (written < 0){
|
||||
FAIL_MSG("Could not (fully) extract update! Aborting.");
|
||||
break;
|
||||
}
|
||||
tarProgress += written;
|
||||
updatePerc = 95 + (5*tarProgress)/DL.data().size();
|
||||
updatePerc = 95 + (5 * tarProgress) / DL.data().size();
|
||||
}
|
||||
close(tarIn);
|
||||
uint64_t waitCount = 0;
|
||||
|
@ -176,12 +177,13 @@ namespace Controller{
|
|||
#else
|
||||
HTTP::URL url("http://releases.mistserver.org/update.php");
|
||||
#endif
|
||||
url.args = "rel=" + Encodings::URL::encode(RELEASE) + "&pass=" + Encodings::URL::encode(SHARED_SECRET) + "&iid=" + Encodings::URL::encode(instanceId);
|
||||
url.args = "rel=" + Encodings::URL::encode(RELEASE) + "&pass=" + Encodings::URL::encode(SHARED_SECRET) +
|
||||
"&iid=" + Encodings::URL::encode(instanceId);
|
||||
if (DL.get(url) && DL.isOk()){
|
||||
updrInfo = JSON::fromString(DL.data());
|
||||
}else{
|
||||
Log("UPDR", "Error getting update info: "+DL.getStatusText());
|
||||
ret["error"] = "Error getting update info: "+DL.getStatusText();
|
||||
Log("UPDR", "Error getting update info: " + DL.getStatusText());
|
||||
ret["error"] = "Error getting update info: " + DL.getStatusText();
|
||||
return ret;
|
||||
}
|
||||
if (!updrInfo){
|
||||
|
@ -213,5 +215,4 @@ namespace Controller{
|
|||
/// Causes the updater thread to download an update, if available
|
||||
void checkUpdates(){updatePerc = 1;}// CheckUpdates
|
||||
|
||||
}
|
||||
|
||||
}// namespace Controller
|
||||
|
|
|
@ -10,5 +10,4 @@ namespace Controller{
|
|||
JSON::Value checkUpdateInfo();
|
||||
void checkUpdates();
|
||||
void insertUpdateInfo(JSON::Value &ret);
|
||||
}
|
||||
|
||||
}// namespace Controller
|
||||
|
|
|
@ -1,100 +1,97 @@
|
|||
#include <stdlib.h>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/timing.h>
|
||||
#include "controller_uplink.h"
|
||||
#include "controller_api.h"
|
||||
#include "controller_capabilities.h"
|
||||
#include "controller_connectors.h"
|
||||
#include "controller_limits.h"
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_storage.h"
|
||||
#include "controller_streams.h"
|
||||
#include "controller_connectors.h"
|
||||
#include "controller_capabilities.h"
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_updater.h"
|
||||
#include "controller_limits.h"
|
||||
#include "controller_api.h"
|
||||
#include "controller_uplink.h"
|
||||
#include <mist/auth.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/timing.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void Controller::uplinkConnection(void * np) {
|
||||
void Controller::uplinkConnection(void *np){
|
||||
std::string uplink_name = Controller::conf.getString("uplink-name");
|
||||
std::string uplink_pass = Controller::conf.getString("uplink-pass");
|
||||
std::string uplink_addr = Controller::conf.getString("uplink");
|
||||
std::string uplink_host = "";
|
||||
std::string uplink_chal = "";
|
||||
int uplink_port = 0;
|
||||
if (uplink_addr.size() > 0) {
|
||||
if (uplink_addr.size() > 0){
|
||||
size_t colon = uplink_addr.find(':');
|
||||
if (colon != std::string::npos && colon != 0 && colon != uplink_addr.size()) {
|
||||
if (colon != std::string::npos && colon != 0 && colon != uplink_addr.size()){
|
||||
uplink_host = uplink_addr.substr(0, colon);
|
||||
uplink_port = atoi(uplink_addr.substr(colon + 1, std::string::npos).c_str());
|
||||
Controller::Log("CONF", "Connection to uplink enabled on host " + uplink_host + " and port " + uplink_addr.substr(colon + 1, std::string::npos));
|
||||
Controller::Log("CONF", "Connection to uplink enabled on host " + uplink_host + " and port " +
|
||||
uplink_addr.substr(colon + 1, std::string::npos));
|
||||
}
|
||||
}
|
||||
//cancel the whole thread if no uplink is set
|
||||
if (!uplink_port) {
|
||||
return;
|
||||
}
|
||||
// cancel the whole thread if no uplink is set
|
||||
if (!uplink_port){return;}
|
||||
|
||||
uint64_t lastSend = Util::epoch() - 5;
|
||||
Socket::Connection uplink;
|
||||
while (Controller::conf.is_active) {
|
||||
if (!uplink) {
|
||||
while (Controller::conf.is_active){
|
||||
if (!uplink){
|
||||
INFO_MSG("Connecting to uplink at %s:%u", uplink_host.c_str(), uplink_port);
|
||||
uplink.open(uplink_host, uplink_port, true);
|
||||
}
|
||||
if (uplink) {
|
||||
if (uplink.spool()) {
|
||||
if (uplink.Received().available(9)) {
|
||||
if (uplink){
|
||||
if (uplink.spool()){
|
||||
if (uplink.Received().available(9)){
|
||||
std::string data = uplink.Received().copy(8);
|
||||
if (data.substr(0, 4) != "DTSC") {
|
||||
if (data.substr(0, 4) != "DTSC"){
|
||||
uplink.Received().clear();
|
||||
continue;
|
||||
}
|
||||
unsigned int size = ntohl(*(const unsigned int *)(data.data() + 4));
|
||||
if (uplink.Received().available(8 + size)) {
|
||||
if (uplink.Received().available(8 + size)){
|
||||
std::string packet = uplink.Received().remove(8 + size);
|
||||
DTSC::Scan inScan = DTSC::Packet(packet.data(), packet.size()).getScan();
|
||||
if (!inScan){continue;}
|
||||
JSON::Value curVal;
|
||||
//Parse config and streams from the request.
|
||||
// Parse config and streams from the request.
|
||||
if (inScan.hasMember("authorize") && inScan.getMember("authorize").hasMember("challenge")){
|
||||
uplink_chal = inScan.getMember("authorize").getMember("challenge").asString();
|
||||
}
|
||||
if (inScan.hasMember("streams")) {
|
||||
if (inScan.hasMember("streams")){
|
||||
curVal = inScan.getMember("streams").asJSON();
|
||||
Controller::CheckStreams(curVal, Controller::Storage["streams"]);
|
||||
}
|
||||
if (inScan.hasMember("addstream")) {
|
||||
if (inScan.hasMember("addstream")){
|
||||
curVal = inScan.getMember("addstream").asJSON();
|
||||
Controller::AddStreams(curVal, Controller::Storage["streams"]);
|
||||
}
|
||||
if (inScan.hasMember("deletestream")) {
|
||||
if (inScan.hasMember("deletestream")){
|
||||
curVal = inScan.getMember("deletestream").asJSON();
|
||||
//if array, delete all elements
|
||||
//if object, delete all entries
|
||||
//if string, delete just the one
|
||||
if (curVal.isString()) {
|
||||
// if array, delete all elements
|
||||
// if object, delete all entries
|
||||
// if string, delete just the one
|
||||
if (curVal.isString()){
|
||||
Controller::Storage["streams"].removeMember(curVal.asStringRef());
|
||||
}
|
||||
if (curVal.isArray()) {
|
||||
jsonForEach(curVal, it) {
|
||||
if (curVal.isArray()){
|
||||
jsonForEach(curVal, it){
|
||||
Controller::Storage["streams"].removeMember(it->asString());
|
||||
}
|
||||
}
|
||||
if (curVal.isObject()) {
|
||||
jsonForEach(curVal, it) {
|
||||
Controller::Storage["streams"].removeMember(it.key());
|
||||
}
|
||||
if (curVal.isObject()){
|
||||
jsonForEach(curVal, it){Controller::Storage["streams"].removeMember(it.key());}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Util::epoch() - lastSend >= 2) {
|
||||
if (Util::epoch() - lastSend >= 2){
|
||||
JSON::Value data;
|
||||
data["tracks"].null();//make sure the data is encoded as DTSC
|
||||
data["tracks"].null(); // make sure the data is encoded as DTSC
|
||||
if (uplink_chal.size()){
|
||||
data["authorize"]["username"] = uplink_name;
|
||||
data["authorize"]["password"] = Secure::md5( Secure::md5(uplink_pass) + uplink_chal);
|
||||
data["authorize"]["password"] = Secure::md5(Secure::md5(uplink_pass) + uplink_chal);
|
||||
}
|
||||
JSON::Value totalsRequest;
|
||||
Controller::fillClients(totalsRequest, data["clients"]);
|
||||
|
@ -115,9 +112,9 @@ void Controller::uplinkConnection(void * np) {
|
|||
data.sendTo(uplink);
|
||||
lastSend = Util::epoch();
|
||||
}
|
||||
} else {
|
||||
}else{
|
||||
Controller::Log("UPLK", "Could not connect to uplink.");
|
||||
}
|
||||
Util::wait(2000);//wait for 2.5 seconds
|
||||
Util::wait(2000); // wait for 2.5 seconds
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
namespace Controller {
|
||||
void uplinkConnection(void * np);
|
||||
namespace Controller{
|
||||
void uplinkConnection(void *np);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue