Updated triggers, implemented LIVE_BANDWIDTH trigger

This commit is contained in:
Thulinma 2017-01-19 15:07:36 +01:00
parent 6a68d86a0e
commit cab87a6425
6 changed files with 319 additions and 324 deletions

View file

@ -95,7 +95,7 @@ static const char * DBG_LVL_LIST[] = {"NONE", "FAIL", "ERROR", "WARN", "INFO", "
#define SHM_TRACK_DATA "MstDATA%s@%lu_%lu" //%s stream name, %lu track ID, %lu page # #define SHM_TRACK_DATA "MstDATA%s@%lu_%lu" //%s stream name, %lu track ID, %lu page #
#define SHM_STATISTICS "MstSTAT" #define SHM_STATISTICS "MstSTAT"
#define SHM_USERS "MstUSER%s" //%s stream name #define SHM_USERS "MstUSER%s" //%s stream name
#define SHM_TRIGGER "MstTRIG%s" //%s trigger name #define SHM_TRIGGER "MstTRGR%s" //%s trigger name
#define SEM_LIVE "/MstLIVE%s" //%s stream name #define SEM_LIVE "/MstLIVE%s" //%s stream name
#define SEM_INPUT "/MstInpt%s" //%s stream name #define SEM_INPUT "/MstInpt%s" //%s stream name
#define SEM_CONF "/MstConfLock" #define SEM_CONF "/MstConfLock"

View file

@ -5,239 +5,201 @@
/// Triggers are the preferred way of responding to server events. Each trigger has a name and a payload, and may be stream-specific or global. /// Triggers are the preferred way of responding to server events. Each trigger has a name and a payload, and may be stream-specific or global.
/// ///
/// Triggers may be handled by a URL or an executable. If the handler contains ://, a HTTP URL is assumed. Otherwise, an executable is assumed. /// Triggers may be handled by a URL or an executable. If the handler contains ://, a HTTP URL is assumed. Otherwise, an executable is assumed.
/// If handled by an URL, a POST request is sent to the URL with an extra X-Trigger header containing the trigger name and the payload as the POST body. /// If handled by an URL, a POST request is sent to the URL with an extra X-Trigger header containing the trigger name and the payload as the
/// If handled by an executable, it's started with the trigger name as its only argument, and the payload is piped into the executable over standard input. /// POST body.
/// If handled by an executable, it's started with the trigger name as its only argument, and the payload is piped into the executable over
/// standard input.
/// ///
/// Currently, all triggers are handled asynchronously and responses (if any) are completely ignored. In the future this may change. /// Currently, all triggers are handled asynchronously and responses (if any) are completely ignored. In the future this may change.
/// ///
#include <string.h>//for strncmp
#include "triggers.h" #include "triggers.h"
#include "http_parser.h"//for sending http request #include "bitfields.h" //for strToBool
#include "defines.h" //for FAIL_MSG and INFO_MSG #include "defines.h" //for FAIL_MSG and INFO_MSG
#include "procs.h" //for StartPiped #include "http_parser.h" //for sending http request
#include "procs.h" //for StartPiped
#include "shared_memory.h" #include "shared_memory.h"
#include "bitfields.h" //for strToBool #include "util.h"
#include <string.h> //for strncmp
namespace Triggers{ namespace Triggers{
///\brief Handles a trigger by sending a payload to a destination. ///\brief Handles a trigger by sending a payload to a destination.
///\param trigger Trigger event type. ///\param trigger Trigger event type.
///\param value Destination. This can be an (HTTP)URL, or an absolute path to a binary/script ///\param value Destination. This can be an (HTTP)URL, or an absolute path to a binary/script
///\param payload This data will be sent to the destionation URL/program ///\param payload This data will be sent to the destionation URL/program
///\param sync If true, handler is executed blocking and uses the response data. ///\param sync If true, handler is executed blocking and uses the response data.
///\returns String, false if further processing should be aborted. ///\returns String, false if further processing should be aborted.
std::string handleTrigger(const std::string &trigger, const std::string &value, const std::string &payload, int sync){ std::string handleTrigger(const std::string &trigger, const std::string &value, const std::string &payload, int sync){
if(!value.size()){ if (!value.size()){
WARN_MSG("Trigger requested with empty destination"); WARN_MSG("Trigger requested with empty destination");
return "true";
}
INFO_MSG("Executing %s trigger: %s (%s)", trigger.c_str(), value.c_str(), sync ? "blocking" : "asynchronous");
if (value.substr(0, 7) == "http://"){ //interpret as url
std::string url = value.substr(value.find("://") + 3); //contains server+url
std::string server = url.substr(0, url.find('/'));
int port=80;
if (server.find(':') != std::string::npos){
port = atoi(server.data() + server.find(':') + 1);
server.erase(server.find(':'));
}
url = url.substr(url.find('/'));
Socket::Connection conn(server,port,false);
HTTP::Parser H;
H.url = url;
H.method = "POST";
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
H.SetHeader("Content-Type", "application/x-www-form-urlencoded");
H.SetHeader("X-Trigger", trigger);
H.SetBody(payload);
H.SendRequest(conn);
H.Clean();
if(sync){ //if sync!=0 wait for response
while (conn && (!conn.spool() || !H.Read(conn))) {}
conn.close();
/// \todo Handle errors!
return H.body;
}else{
conn.close();
return "true"; return "true";
} }
} else { //send payload to stdin of newly forked process INFO_MSG("Executing %s trigger: %s (%s)", trigger.c_str(), value.c_str(), sync ? "blocking" : "asynchronous");
int fdIn=-1; if (value.substr(0, 7) == "http://"){// interpret as url
int fdOut=-1; std::string url = value.substr(value.find("://") + 3); // contains server+url
int fdErr=-1; std::string server = url.substr(0, url.find('/'));
int port = 80;
char * argv[3]; if (server.find(':') != std::string::npos){
argv[0]=(char*)value.c_str(); port = atoi(server.data() + server.find(':') + 1);
argv[1]=(char*)trigger.c_str(); server.erase(server.find(':'));
argv[2]=NULL;
pid_t myProc = Util::Procs::StartPiped(argv, &fdIn,&fdOut,&fdErr); //start new process and return stdin file desc.
if ( fdIn == -1 || fdOut == -1 || fdErr == -1 ){ //verify fdIn
FAIL_MSG("StartPiped returned invalid fd");
return "true";/// \todo Return true/false based on config here.
}
write(fdIn, payload.data(), payload.size());
shutdown(fdIn, SHUT_RDWR);
close(fdIn);
if(sync){ //if sync!=0 wait for response
while (Util::Procs::isActive(myProc)) {
Util::sleep(100);
} }
std::string ret; url = url.substr(url.find('/'));
FILE * outFile = fdopen(fdOut, "r");
char * fileBuf = 0; Socket::Connection conn(server, port, false);
size_t fileBufLen = 0; HTTP::Parser H;
while (!(feof(outFile) || ferror(outFile)) && (getline(&fileBuf, &fileBufLen, outFile) != -1)) { H.url = url;
ret += fileBuf; H.method = "POST";
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
H.SetHeader("Content-Type", "application/x-www-form-urlencoded");
H.SetHeader("X-Trigger", trigger);
H.SetBody(payload);
H.SendRequest(conn);
H.Clean();
if (sync){// if sync!=0 wait for response
while (conn && (!conn.spool() || !H.Read(conn))){}
conn.close();
/// \todo Handle errors!
return H.body;
}else{
conn.close();
return "true";
}
}else{// send payload to stdin of newly forked process
int fdIn = -1;
int fdOut = -1;
int fdErr = -1;
char *argv[3];
argv[0] = (char *)value.c_str();
argv[1] = (char *)trigger.c_str();
argv[2] = NULL;
pid_t myProc = Util::Procs::StartPiped(argv, &fdIn, &fdOut, &fdErr); // start new process and return stdin file desc.
if (fdIn == -1 || fdOut == -1 || fdErr == -1){// verify fdIn
FAIL_MSG("StartPiped returned invalid fd");
return "true"; /// \todo Return true/false based on config here.
}
write(fdIn, payload.data(), payload.size());
shutdown(fdIn, SHUT_RDWR);
close(fdIn);
if (sync){// if sync!=0 wait for response
while (Util::Procs::isActive(myProc)){Util::sleep(100);}
std::string ret;
FILE *outFile = fdopen(fdOut, "r");
char *fileBuf = 0;
size_t fileBufLen = 0;
while (!(feof(outFile) || ferror(outFile)) && (getline(&fileBuf, &fileBufLen, outFile) != -1)){ret += fileBuf;}
fclose(outFile);
free(fileBuf);
close(fdOut);
close(fdErr);
return ret;
} }
fclose(outFile);
free(fileBuf);
close(fdOut); close(fdOut);
close(fdErr); close(fdErr);
return ret; return "true";
} }
close(fdOut);
close(fdErr);
return "true";
}
}
static std::string empty;
///\brief Checks if one or more triggers are defined that should be handled for all streams (for a trigger event type)
///\param type Trigger event type.
///\return returns true, if so
///calls doTrigger with dryRun set to true
bool shouldTrigger(const std::string type){ //returns true if a trigger of the specified type should be handled for all streams
static std::string empty;
empty.clear();
return doTrigger(type, empty, empty, true, empty);
}
///\brief returns true if a trigger of the specified type should be handled for a specified stream (, or entire server)
///\param type Trigger event type.
///\param streamName the stream to be handled
///\return returns true if so
///calls doTrigger with dryRun set to true
bool shouldTrigger(const std::string type, const std::string &streamName){ //returns true if a trigger of the specified type should be handled for a specified stream (, or entire server)
empty.clear();
return doTrigger(type, empty, streamName, true, empty);
}
///\brief handles triggers for a specific trigger event type, without a payload, server-wide
///\param type Trigger event type.
///\returns Boolean, false if further processing should be aborted.
///calls doTrigger with dryRun set to false
bool doTrigger(const std::string type){
empty.clear();
return doTrigger(type, empty, empty, false, empty);
}
///\brief handles triggers for a specific trigger event type, with a payload, server-wide
///\param type Trigger event type.
///\param payload Trigger type-specific data
///\returns Boolean, false if further processing should be aborted.
///calls doTrigger with dryRun set to false
bool doTrigger(const std::string type, const std::string &payload){
empty.clear();
return doTrigger(type, payload, empty, false, empty);
}
///\brief handles triggers for a specific trigger event type, with a payload, for a specified stream, and/or server-wide
///\param type Trigger event type.
///\param payload Trigger type-specific data
///\param streamName The name of a stream.
///\returns Boolean, false if further processing should be aborted.
///calls doTrigger with dryRun set to false
bool doTrigger(const std::string type, const std::string &payload, const std::string &streamName){
empty.clear();
return doTrigger(type, payload, streamName, false, empty);
}
///\brief
///\param type Trigger event type
///\param payload Trigger type-specific data
///\param streamName Name of a stream to check for stream-specific triggers
///\param dryRun determines the mode of operation for this function
///\param response Returns the last received response by reference
///\returns Boolean, false if further processing should be aborted
///This function attempts to open and parse a shared memory page with the config for a trigger event type, in order to parse the triggers defined for that trigger event type.
///The function can be used for two separate purposes, determined by the value of dryRun
///-if this function is called with dryRun==true (for example, from a handleTrigger function), the return value will be true, if at least one trigger should be handled for the requested type/stream.
///this can be used to make sure a payload is only generated if at least one trigger should be handled.
///-if this function is called with dryRun==false (for example, from one of the overloaded doTrigger functions), handleTrigger is called for all configured triggers. In that case, the return value does not matter, it will probably be false in all cases.
bool doTrigger(const std::string type, const std::string &payload, const std::string &streamName, bool dryRun, std::string & response){
//open SHM page for this type:
char thisPageName[NAME_BUFFER_SIZE];
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRIGGER, type.c_str());
IPC::sharedPage typePage(thisPageName, 8*1024, false, false);
if(!typePage.mapped){ //page doesn't exist?
HIGH_MSG("No triggers for %s defined: list does not exist", type.c_str());
return false;
} }
char* bytepos = typePage.mapped; //not checking page size. will probably be fine. static std::string usually_empty;
char* startBytepos=bytepos;
unsigned int totalLen = ((unsigned int *)bytepos)[0];
bool retVal = true;
VERYHIGH_MSG("Parsing %lu bytes of triggers for %s, stream: %s", totalLen, type.c_str(), streamName.c_str());
std::string uri;
unsigned int sync=0;
while( totalLen != 0 && bytepos < typePage.mapped + typePage.len ){ ///\brief returns true if a trigger of the specified type should be handled for a specified stream (, or entire server)
unsigned int uriLen = ((unsigned int *)bytepos)[1]; ///\param type Trigger event type.
bytepos+=4+4; ///\param streamName the stream to be handled
uri=std::string(bytepos,uriLen); ///\return returns true if so
bytepos+=uriLen; /// calls doTrigger with dryRun set to true
sync=bytepos[0]; /// returns true if a trigger of the specified type should be
bytepos++; /// handled for a specified stream (, or entire server)
bool shouldTrigger(const std::string & type, const std::string & streamName, bool paramsCB(const char *, const void *), const void * extraParam){
usually_empty.clear();
return doTrigger(type, empty, streamName, true, usually_empty, paramsCB, extraParam);
}
bool isHandled = false; ///\brief handles triggers for a specific trigger event type, with a payload, for a specified stream, and/or server-wide
if(totalLen>((unsigned int)(bytepos-startBytepos))){ ///\param type Trigger event type.
while( totalLen>((unsigned int)(bytepos-startBytepos)) ){ ///\param payload Trigger type-specific data
unsigned int stringLen=((unsigned int *)bytepos)[0]; ///\param streamName The name of a stream.
bytepos+=4; ///\returns Boolean, false if further processing should be aborted.
size_t splitter = streamName.find_first_of("+ "); /// calls doTrigger with dryRun set to false
if ((streamName.size() == stringLen || splitter == stringLen) && strncmp(bytepos, streamName.c_str(), stringLen) == 0){ bool doTrigger(const std::string & type, const std::string &payload, const std::string &streamName){
usually_empty.clear();
return doTrigger(type, payload, streamName, false, usually_empty);
}
///\brief
///\param type Trigger event type
///\param payload Trigger type-specific data
///\param streamName Name of a stream to check for stream-specific triggers
///\param dryRun determines the mode of operation for this function
///\param response Returns the last received response by reference
///\returns Boolean, false if further processing should be aborted
/// This function attempts to open and parse a shared memory page with the config for a trigger event type, in order to parse the triggers
/// defined for that trigger event type.
/// The function can be used for two separate purposes, determined by the value of dryRun
///-if this function is called with dryRun==true (for example, from a handleTrigger function), the return value will be true, if at least one
///trigger should be handled for the requested type/stream.
/// this can be used to make sure a payload is only generated if at least one trigger should be handled.
///-if this function is called with dryRun==false (for example, from one of the overloaded doTrigger functions), handleTrigger is called for
///all configured triggers. In that case, the return value does not matter, it will probably be false in all cases.
bool doTrigger(const std::string & type, const std::string &payload, const std::string &streamName, bool dryRun, std::string &response, bool paramsCB(const char *, const void *), const void * extraParam){
// open SHM page for this type:
char thisPageName[NAME_BUFFER_SIZE];
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRIGGER, type.c_str());
IPC::sharedPage typePage(thisPageName, 8 * 1024, false, false);
if (!typePage.mapped){// page doesn't exist?
VERYHIGH_MSG("No triggers for %s found", type.c_str());
return false;
}
Util::RelAccX trigs(typePage.mapped, false);
if (!trigs.isReady()){
WARN_MSG("No triggers for %s: list not ready", type.c_str());
return false;
}
size_t splitter = streamName.find_first_of("+ ");
bool retVal = true;
for (uint32_t i = 0; i < trigs.getRCount(); ++i){
std::string uri = std::string(trigs.getPointer("url", i));
uint8_t sync = trigs.getInt("sync", i);
char * strPtr = trigs.getPointer("streams", i);
uint32_t pLen = trigs.getSize("streams");
uint32_t bPos = 0;
bool isHandled = !streamName.size();
while (bPos + 4 < pLen){
uint32_t stringLen = ((unsigned int *)(strPtr+bPos))[0];
if (bPos + 4 + stringLen > pLen || !stringLen){break;}
if ((streamName.size() == stringLen || splitter == stringLen) && strncmp(strPtr+bPos+4, streamName.data(), stringLen) == 0){
isHandled = true; isHandled = true;
} }
bytepos+=stringLen; bPos += stringLen + 4;
} }
if (!streamName.size()){ // no streams explicitly defined for this trigger, return true for all streams.
if (bPos <= 4){
isHandled = true; isHandled = true;
} }
} else if(totalLen==((unsigned int)(bytepos-startBytepos))){
//no streams explicitly defined for this trigger, return true for all streams.
isHandled = true;
}
if (isHandled){ if (isHandled && paramsCB){
VERYHIGH_MSG("%s trigger handled by %s", type.c_str(), uri.c_str()); isHandled = paramsCB(trigs.getPointer("params", i), extraParam);
if(dryRun){ }
return true;
if (isHandled){
INFO_MSG("%s trigger handled by %s", type.c_str(), uri.c_str());
if (dryRun){return true;}
response = handleTrigger(type, uri, payload, sync); // do it.
retVal &= Util::stringToBool(response);
} }
response = handleTrigger(type,uri,payload,sync); //do it.
retVal &= Util::stringToBool(response);
} }
if(totalLen!=((unsigned int)(bytepos-startBytepos))){ //if this is not the case, something bad might have happened. if (dryRun){
ERROR_MSG("Error in %s trigger, totalLen: %d current position from startBytepos: %d", type.c_str(),totalLen, (unsigned int)(bytepos-startBytepos)); return false;
break; //stop reading hypothetical garbage }else{
return retVal;
} }
startBytepos=startBytepos+totalLen; //init next iteration
bytepos=startBytepos;
totalLen = ((unsigned int *)bytepos)[0]; //read next size
}
if (dryRun){
return false;
}else{
return retVal;
} }
} }
} //end namespace Controller

View file

@ -2,13 +2,14 @@
#include <string> #include <string>
namespace Triggers{ namespace Triggers{
bool doTrigger(const std::string triggerType, const std::string &payload, const std::string &streamName, bool dryRun, std::string & response);
static const std::string empty;
bool doTrigger(const std::string & triggerType, const std::string &payload, const std::string &streamName, bool dryRun, std::string &response, bool paramsCB(const char *, const void *) = 0, const void * extraParam = 0);
std::string handleTrigger(const std::string &triggerType, const std::string &value, const std::string &payload, int sync); std::string handleTrigger(const std::string &triggerType, const std::string &value, const std::string &payload, int sync);
//All of the below are just shorthands for specific usage of the doTrigger function above: // All of the below are just shorthands for specific usage of the doTrigger function above:
bool shouldTrigger(const std::string triggerType); bool shouldTrigger(const std::string & triggerType, const std::string &streamName = empty, bool paramsCB(const char *, const void *) = 0, const void * extraParam = 0);
bool shouldTrigger(const std::string triggerType, const std::string &streamName); bool doTrigger(const std::string & triggerType, const std::string & payload = empty, const std::string & streamName = empty);
bool doTrigger(const std::string triggerType);
bool doTrigger(const std::string triggerType, const std::string &payload);
bool doTrigger(const std::string triggerType, const std::string &payload, const std::string &streamName);
} }

View file

@ -37,6 +37,8 @@ namespace Util {
#define RAX_128STRING 0x33 #define RAX_128STRING 0x33
#define RAX_256STRING 0x34 #define RAX_256STRING 0x34
#define RAX_RAW 0x40 #define RAX_RAW 0x40
#define RAX_256RAW 0x44
#define RAX_512RAW 0x45
/// Reliable Access class. /// Reliable Access class.
/// Provides reliable access to memory data structures, using dynamic static offsets and a status field. /// Provides reliable access to memory data structures, using dynamic static offsets and a status field.

View file

@ -1,17 +1,18 @@
#include <sys/stat.h>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <mist/timing.h>
#include <mist/shared_memory.h>
#include <mist/defines.h>
#include "controller_storage.h" #include "controller_storage.h"
#include "controller_capabilities.h" #include "controller_capabilities.h"
#include <mist/triggers.h>//LTS #include <algorithm>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/shared_memory.h>
#include <mist/timing.h>
#include <mist/triggers.h> //LTS
#include <mist/util.h> //LTS
#include <sys/stat.h>
///\brief Holds everything unique to the controller. ///\brief Holds everything unique to the controller.
namespace Controller { namespace Controller{
std::string instanceId; ///instanceId (previously uniqId) is first set in controller.cpp before licensing or update calls. std::string instanceId; /// instanceId (previously uniqId) is first set in controller.cpp before licensing or update calls.
Util::Config conf; Util::Config conf;
JSON::Value Storage; ///< Global storage of data. JSON::Value Storage; ///< Global storage of data.
tthread::mutex configMutex; tthread::mutex configMutex;
@ -30,13 +31,13 @@ namespace Controller {
m.append(kind); m.append(kind);
m.append(message); m.append(message);
Storage["log"].append(m); Storage["log"].append(m);
Storage["log"].shrink(100); //limit to 100 log messages Storage["log"].shrink(100); // limit to 100 log messages
time_t rawtime; time_t rawtime;
struct tm * timeinfo; struct tm *timeinfo;
char buffer [100]; char buffer[100];
time (&rawtime); time(&rawtime);
timeinfo = localtime (&rawtime); timeinfo = localtime(&rawtime);
strftime(buffer,100,"%F %H:%M:%S",timeinfo); strftime(buffer, 100, "%F %H:%M:%S", timeinfo);
std::cout << "[" << buffer << "] " << kind << ": " << message << std::endl; std::cout << "[" << buffer << "] " << kind << ": " << message << std::endl;
logCounter++; logCounter++;
} }
@ -56,22 +57,18 @@ namespace Controller {
/// Debug messages are automatically converted into Log messages. /// Debug messages are automatically converted into Log messages.
/// Closes the file descriptor on read error. /// Closes the file descriptor on read error.
/// \param err File descriptor of the stderr output of the process to monitor. /// \param err File descriptor of the stderr output of the process to monitor.
void handleMsg(void * err){ void handleMsg(void *err){
char buf[1024]; char buf[1024];
FILE * output = fdopen((long long int)err, "r"); FILE *output = fdopen((long long int)err, "r");
while (fgets(buf, 1024, output)){ while (fgets(buf, 1024, output)){
unsigned int i = 0; unsigned int i = 0;
while (i < 9 && buf[i] != '|' && buf[i] != 0){ while (i < 9 && buf[i] != '|' && buf[i] != 0){++i;}
++i;
}
unsigned int j = i; unsigned int j = i;
while (j < 1024 && buf[j] != '\n' && buf[j] != 0){ while (j < 1024 && buf[j] != '\n' && buf[j] != 0){++j;}
++j;
}
buf[j] = 0; buf[j] = 0;
if(i < 9){ if (i < 9){
buf[i] = 0; buf[i] = 0;
Log(buf,buf+i+1); Log(buf, buf + i + 1);
}else{ }else{
printf("%s", buf); printf("%s", buf);
} }
@ -83,9 +80,11 @@ namespace Controller {
/// Writes the current config to shared memory to be used in other processes /// Writes the current config to shared memory to be used in other processes
/// \triggers /// \triggers
/// The `"SYSTEM_START"` trigger is global, and is ran as soon as the server configuration is first stable. It has no payload. If cancelled, the system immediately shuts down again. /// The `"SYSTEM_START"` trigger is global, and is ran as soon as the server configuration is first stable. It has no payload. If cancelled,
/// the system immediately shuts down again.
/// \n /// \n
/// The `"SYSTEM_CONFIG"` trigger is global, and is ran every time the server configuration is updated. Its payload is the new configuration in JSON format. This trigger cannot be cancelled. /// The `"SYSTEM_CONFIG"` trigger is global, and is ran every time the server configuration is updated. Its payload is the new configuration in
/// JSON format. This trigger cannot be cancelled.
void writeConfig(){ void writeConfig(){
static JSON::Value writeConf; static JSON::Value writeConf;
bool changed = false; bool changed = false;
@ -104,7 +103,7 @@ namespace Controller {
VERYHIGH_MSG("Saving new config because of edit in capabilities"); VERYHIGH_MSG("Saving new config because of edit in capabilities");
changed = true; changed = true;
} }
if (!changed){return;}//cancel further processing if no changes if (!changed){return;}// cancel further processing if no changes
static IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, true); static IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, true);
if (!mistConfOut.mapped){ if (!mistConfOut.mapped){
@ -112,90 +111,99 @@ namespace Controller {
return; return;
} }
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1); IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
//lock semaphore // lock semaphore
configLock.wait(); configLock.wait();
//write config // write config
std::string temp = writeConf.toPacked(); std::string temp = writeConf.toPacked();
memcpy(mistConfOut.mapped, temp.data(), std::min(temp.size(), (size_t)mistConfOut.len)); memcpy(mistConfOut.mapped, temp.data(), std::min(temp.size(), (size_t)mistConfOut.len));
//unlock semaphore // unlock semaphore
configLock.post(); configLock.post();
/*LTS-START*/ /*LTS-START*/
static std::map<std::string,IPC::sharedPage> pageForType; //should contain one page for every trigger type static std::map<std::string, IPC::sharedPage> pageForType; // should contain one page for every trigger type
char tmpBuf[NAME_BUFFER_SIZE]; char tmpBuf[NAME_BUFFER_SIZE];
//for all shm pages that hold triggers // for all shm pages that hold triggers
pageForType.clear(); pageForType.clear();
if( writeConf["config"]["triggers"].size() ){//if triggers are defined... if (writeConf["config"]["triggers"].size()){
jsonForEach(writeConf["config"]["triggers"], it){//for all types defined in config jsonForEach(writeConf["config"]["triggers"], it){
snprintf(tmpBuf,NAME_BUFFER_SIZE,SHM_TRIGGER,(it.key()).c_str()); //create page snprintf(tmpBuf, NAME_BUFFER_SIZE, SHM_TRIGGER, (it.key()).c_str());
pageForType[it.key()].init(tmpBuf, 8*1024, true, false);// todo: should this be false/why?? pageForType[it.key()].init(tmpBuf, 32 * 1024, true, false);
char * bytePos=pageForType[it.key()].mapped; Util::RelAccX tPage(pageForType[it.key()].mapped, false);
tPage.addField("url", RAX_128STRING);
tPage.addField("sync", RAX_UINT);
tPage.addField("streams", RAX_256RAW);
tPage.addField("params", RAX_128STRING);
tPage.setReady();
uint32_t i = 0;
uint32_t max = (32 * 1024 - tPage.getOffset()) / tPage.getRSize();
//write data to page // write data to page
jsonForEach(*it, triggIt){ //for all defined jsonForEach(*it, triggIt){
unsigned int tmpUrlSize=(*triggIt)[(unsigned int) 0].asStringRef().size(); if (i >= max){
unsigned int tmpStreamNames=0;// (*triggIt)[2ul].packedSize(); ERROR_MSG("Not all %s triggers fit on the memory page!", (it.key()).c_str());
std::string namesArray=""; break;
}
if( (triggIt->size() >= 3) && (*triggIt)[2ul].size()){ if (triggIt->isArray()){
jsonForEach((*triggIt)[2ul], shIt){ tPage.setString("url", (*triggIt)[0u].asStringRef(), i);
unsigned int tmpLen=shIt->asString().size(); tPage.setInt("sync", ((*triggIt)[1u].asBool() ? 1 : 0), i);
tmpStreamNames+= 4+tmpLen; char *strmP = tPage.getPointer("streams", i);
//INFO_MSG("adding string: %s len: %d", shIt->asString().c_str() , tmpLen ); if (strmP){
((unsigned int*)tmpBuf)[0] = tmpLen; //NOTE: namesArray may be replaced by writing directly to tmpBuf. ((unsigned int *)strmP)[0] = 0; // reset first 4 bytes of stream list pointer
namesArray.append(tmpBuf,4); if ((triggIt->size() >= 3) && (*triggIt)[2u].size()){
namesArray.append(shIt->asString()); std::string namesArray;
jsonForEach((*triggIt)[2u], shIt){
((unsigned int *)tmpBuf)[0] = shIt->asString().size();
namesArray.append(tmpBuf, 4);
namesArray.append(shIt->asString());
}
if (namesArray.size()){memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));}
}
} }
} }
unsigned int totalLen=9+tmpUrlSize+tmpStreamNames; //4Btotal len, 4Burl len ,XB tmpurl, 1B sync , XB tmpstreamnames
if(totalLen > (pageForType[it.key()].len-(bytePos-pageForType[it.key()].mapped)) ){ //check if totalLen fits on the page if (triggIt->isObject()){
ERROR_MSG("trigger does not fit on page. size: %d bytes left on page: %d skipping.",totalLen,(pageForType[it.key()].len-(bytePos-pageForType[it.key()].mapped))); //doesnt fit if (!triggIt->isMember("handler")){continue;}
continue; tPage.setString("url", (*triggIt)["handler"].asStringRef(), i);
tPage.setInt("sync", ((*triggIt)["sync"].asBool() ? 1 : 0), i);
char *strmP = tPage.getPointer("streams", i);
if (strmP){
((unsigned int *)strmP)[0] = 0; // reset first 4 bytes of stream list pointer
if ((triggIt->isMember("streams")) && (*triggIt)["streams"].size()){
std::string namesArray;
jsonForEach((*triggIt)["streams"], shIt){
((unsigned int *)tmpBuf)[0] = shIt->asString().size();
namesArray.append(tmpBuf, 4);
namesArray.append(shIt->asString());
}
if (namesArray.size()){memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));}
}
}
if (triggIt->isMember("params")){
tPage.setString("params", (*triggIt)["params"].asStringRef(), i);
}else{
tPage.setString("params", "", i);
}
} }
((unsigned int*)bytePos)[0] = totalLen; ++i;
bytePos+=4;
((unsigned int*)bytePos)[0] = tmpUrlSize;
bytePos+=4;
memcpy(bytePos, (*triggIt)[(unsigned int) 0].asStringRef().data(), (*triggIt)[(unsigned int) 0].asStringRef().size());
bytePos+=(*triggIt)[(unsigned int) 0].asStringRef().size();
(bytePos++)[0] = (*triggIt)[1ul].asBool() ? '\001' : '\000';
if(tmpStreamNames){
memcpy(bytePos,namesArray.data(),tmpStreamNames); //contains a string of 4Blen,XBstring pairs
bytePos+=tmpStreamNames;
}
} }
tPage.setRCount(std::min(i, max));
} }
} }
static bool serverStartTriggered; static bool serverStartTriggered;
if(!serverStartTriggered){ if (!serverStartTriggered){
if (!Triggers::doTrigger("SYSTEM_START")){ if (!Triggers::doTrigger("SYSTEM_START")){conf.is_active = false;}
conf.is_active = false; serverStartTriggered++;
} }
serverStartTriggered++; if (Triggers::shouldTrigger("SYSTEM_CONFIG")){
} std::string payload = writeConf.toString();
if (Triggers::shouldTrigger("SYSTEM_CONFIG")){ Triggers::doTrigger("SYSTEM_CONFIG", payload);
std::string payload = writeConf.toString(); }
Triggers::doTrigger("SYSTEM_CONFIG", payload); /*LTS-END*/
}
/*LTS-END*/
} }
} }
/*NOTES:
4B size (total size of entry 9B+XB(URL)+ 0..XB(nameArrayLen)) (if 0x00, stop reading)
4B url_len
XB url
1B async
for(number of strings)
4B stringLen
XB string
)
*/

View file

@ -253,6 +253,14 @@ namespace Mist {
//return is by reference //return is by reference
} }
/*LTS-START*/
static bool liveBW(const char * param, const void * bwPtr){
if (!param || !bwPtr){return false;}
INFO_MSG("Comparing %s to %lu", param, *((uint32_t*)bwPtr));
return JSON::Value(param).asInt() <= *((uint32_t*)bwPtr);
}
/*LTS-END*/
/// \triggers /// \triggers
/// The `"STREAM_BUFFER"` trigger is stream-specific, and is ran whenever the buffer changes state between playable (FULL) or not (EMPTY). It cannot be cancelled. It is possible to receive multiple EMPTY calls without FULL calls in between, as EMPTY is always generated when a stream is unloaded from memory, even if this stream never reached playable state in the first place (e.g. a broadcast was cancelled before filling enough buffer to be playable). Its payload is: /// The `"STREAM_BUFFER"` trigger is stream-specific, and is ran whenever the buffer changes state between playable (FULL) or not (EMPTY). It cannot be cancelled. It is possible to receive multiple EMPTY calls without FULL calls in between, as EMPTY is always generated when a stream is unloaded from memory, even if this stream never reached playable state in the first place (e.g. a broadcast was cancelled before filling enough buffer to be playable). Its payload is:
/// ~~~~~~~~~~~~~~~ /// ~~~~~~~~~~~~~~~
@ -263,10 +271,13 @@ namespace Mist {
void inputBuffer::updateMeta() { void inputBuffer::updateMeta() {
static bool wentDry = false; static bool wentDry = false;
static long long unsigned int lastFragCount = 0xFFFFull; static long long unsigned int lastFragCount = 0xFFFFull;
static uint32_t lastBPS = 0;/*LTS*/
uint32_t currBPS = 0;
long long unsigned int firstms = 0xFFFFFFFFFFFFFFFFull; long long unsigned int firstms = 0xFFFFFFFFFFFFFFFFull;
long long unsigned int lastms = 0; long long unsigned int lastms = 0;
long long unsigned int fragCount = 0xFFFFull; long long unsigned int fragCount = 0xFFFFull;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
currBPS += it->second.bps; /*LTS*/
if (it->second.type == "meta" || !it->second.type.size()) { if (it->second.type == "meta" || !it->second.type.size()) {
continue; continue;
} }
@ -290,20 +301,31 @@ namespace Mist {
} }
} }
/*LTS-START*/ /*LTS-START*/
if (fragCount >= FRAG_BOOT && fragCount != 0xFFFFull && Triggers::shouldTrigger("STREAM_BUFFER")){ if (currBPS != lastBPS){
lastBPS = currBPS;
if (Triggers::shouldTrigger("LIVE_BANDWIDTH", streamName, liveBW, &lastBPS)){
std::string payload = streamName + "\n" + JSON::Value((long long)lastBPS).asStringRef();
if (!Triggers::doTrigger("LIVE_BANDWIDTH", payload, streamName)){
WARN_MSG("Shutting down buffer because bandwidth limit reached!");
config->is_active = false;
userPage.finishEach();
}
}
}
if (fragCount >= FRAG_BOOT && fragCount != 0xFFFFull && Triggers::shouldTrigger("STREAM_BUFFER", streamName)){
JSON::Value stream_details; JSON::Value stream_details;
fillBufferDetails(stream_details); fillBufferDetails(stream_details);
if (lastFragCount == 0xFFFFull) { if (lastFragCount == 0xFFFFull) {
std::string payload = config->getString("streamname") + "\nFULL\n" + stream_details.toString(); std::string payload = streamName + "\nFULL\n" + stream_details.toString();
Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); Triggers::doTrigger("STREAM_BUFFER", payload, streamName);
}else{ }else{
if (stream_details.isMember("issues") != wentDry){ if (stream_details.isMember("issues") != wentDry){
if (stream_details.isMember("issues")){ if (stream_details.isMember("issues")){
std::string payload = config->getString("streamname") + "\nDRY\n" + stream_details.toString(); std::string payload = streamName + "\nDRY\n" + stream_details.toString();
Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); Triggers::doTrigger("STREAM_BUFFER", payload, streamName);
}else{ }else{
std::string payload = config->getString("streamname") + "\nRECOVER\n" + stream_details.toString(); std::string payload = streamName + "\nRECOVER\n" + stream_details.toString();
Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); Triggers::doTrigger("STREAM_BUFFER", payload, streamName);
} }
} }
} }