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_STATISTICS "MstSTAT"
#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_INPUT "/MstInpt%s" //%s stream name
#define SEM_CONF "/MstConfLock"

View file

@ -5,45 +5,48 @@
/// 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.
/// 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 executable, it's started with the trigger name as its only argument, and the payload is piped into the executable over standard input.
/// 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 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.
///
#include <string.h>//for strncmp
#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 "http_parser.h" //for sending http request
#include "procs.h" //for StartPiped
#include "shared_memory.h"
#include "bitfields.h" //for strToBool
#include "util.h"
#include <string.h> //for strncmp
namespace Triggers{
///\brief Handles a trigger by sending a payload to a destination.
///\param trigger Trigger event type.
///\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 sync If true, handler is executed blocking and uses the response data.
///\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){
if(!value.size()){
///\brief Handles a trigger by sending a payload to a destination.
///\param trigger Trigger event type.
///\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 sync If true, handler is executed blocking and uses the response data.
///\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){
if (!value.size()){
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
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;
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);
Socket::Connection conn(server, port, false);
HTTP::Parser H;
H.url = url;
H.method = "POST";
@ -54,8 +57,8 @@ std::string handleTrigger(const std::string &trigger, const std::string &value,
H.SetBody(payload);
H.SendRequest(conn);
H.Clean();
if(sync){ //if sync!=0 wait for response
while (conn && (!conn.spool() || !H.Read(conn))) {}
if (sync){// if sync!=0 wait for response
while (conn && (!conn.spool() || !H.Read(conn))){}
conn.close();
/// \todo Handle errors!
return H.body;
@ -63,35 +66,31 @@ std::string handleTrigger(const std::string &trigger, const std::string &value,
conn.close();
return "true";
}
} else { //send payload to stdin of newly forked process
int fdIn=-1;
int fdOut=-1;
int fdErr=-1;
}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
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.
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);
}
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;
FILE *outFile = fdopen(fdOut, "r");
char *fileBuf = 0;
size_t fileBufLen = 0;
while (!(feof(outFile) || ferror(outFile)) && (getline(&fileBuf, &fileBufLen, outFile) != -1)) {
ret += fileBuf;
}
while (!(feof(outFile) || ferror(outFile)) && (getline(&fileBuf, &fileBufLen, outFile) != -1)){ret += fileBuf;}
fclose(outFile);
free(fileBuf);
close(fdOut);
@ -102,134 +101,98 @@ std::string handleTrigger(const std::string &trigger, const std::string &value,
close(fdErr);
return "true";
}
}
}
static std::string empty;
static std::string usually_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
/// returns true if a trigger of the specified type should be
/// 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);
}
///\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, 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){
usually_empty.clear();
return doTrigger(type, payload, streamName, false, usually_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:
///\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?
HIGH_MSG("No triggers for %s defined: list does not exist", 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;
}
char* bytepos = typePage.mapped; //not checking page size. will probably be fine.
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 ){
unsigned int uriLen = ((unsigned int *)bytepos)[1];
bytepos+=4+4;
uri=std::string(bytepos,uriLen);
bytepos+=uriLen;
sync=bytepos[0];
bytepos++;
bool isHandled = false;
if(totalLen>((unsigned int)(bytepos-startBytepos))){
while( totalLen>((unsigned int)(bytepos-startBytepos)) ){
unsigned int stringLen=((unsigned int *)bytepos)[0];
bytepos+=4;
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("+ ");
if ((streamName.size() == stringLen || splitter == stringLen) && strncmp(bytepos, streamName.c_str(), stringLen) == 0){
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;
}
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;
}
} else if(totalLen==((unsigned int)(bytepos-startBytepos))){
//no streams explicitly defined for this trigger, return true for all streams.
isHandled = true;
if (isHandled && paramsCB){
isHandled = paramsCB(trigs.getPointer("params", i), extraParam);
}
if (isHandled){
VERYHIGH_MSG("%s trigger handled by %s", type.c_str(), uri.c_str());
if(dryRun){
return true;
}
response = handleTrigger(type,uri,payload,sync); //do it.
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);
}
if(totalLen!=((unsigned int)(bytepos-startBytepos))){ //if this is not the case, something bad might have happened.
ERROR_MSG("Error in %s trigger, totalLen: %d current position from startBytepos: %d", type.c_str(),totalLen, (unsigned int)(bytepos-startBytepos));
break; //stop reading hypothetical garbage
}
startBytepos=startBytepos+totalLen; //init next iteration
bytepos=startBytepos;
totalLen = ((unsigned int *)bytepos)[0]; //read next size
}
if (dryRun){
@ -237,7 +200,6 @@ bool doTrigger(const std::string type, const std::string &payload, const std::st
}else{
return retVal;
}
}
}
} //end namespace Controller

View file

@ -2,13 +2,14 @@
#include <string>
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);
//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);
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);
// All of the below are just shorthands for specific usage of the doTrigger function above:
bool shouldTrigger(const std::string & triggerType, const std::string &streamName = empty, bool paramsCB(const char *, const void *) = 0, const void * extraParam = 0);
bool doTrigger(const std::string & triggerType, const std::string & payload = empty, const std::string & streamName = empty);
}

View file

@ -37,6 +37,8 @@ namespace Util {
#define RAX_128STRING 0x33
#define RAX_256STRING 0x34
#define RAX_RAW 0x40
#define RAX_256RAW 0x44
#define RAX_512RAW 0x45
/// Reliable Access class.
/// 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_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.
namespace Controller {
std::string instanceId; ///instanceId (previously uniqId) is first set in controller.cpp before licensing or update calls.
namespace Controller{
std::string instanceId; /// instanceId (previously uniqId) is first set in controller.cpp before licensing or update calls.
Util::Config conf;
JSON::Value Storage; ///< Global storage of data.
tthread::mutex configMutex;
@ -30,13 +31,13 @@ namespace Controller {
m.append(kind);
m.append(message);
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;
struct tm * timeinfo;
char buffer [100];
time (&rawtime);
timeinfo = localtime (&rawtime);
strftime(buffer,100,"%F %H:%M:%S",timeinfo);
struct tm *timeinfo;
char buffer[100];
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(buffer, 100, "%F %H:%M:%S", timeinfo);
std::cout << "[" << buffer << "] " << kind << ": " << message << std::endl;
logCounter++;
}
@ -56,22 +57,18 @@ namespace Controller {
/// Debug messages are automatically converted into Log messages.
/// Closes the file descriptor on read error.
/// \param err File descriptor of the stderr output of the process to monitor.
void handleMsg(void * err){
void handleMsg(void *err){
char buf[1024];
FILE * output = fdopen((long long int)err, "r");
FILE *output = fdopen((long long int)err, "r");
while (fgets(buf, 1024, output)){
unsigned int i = 0;
while (i < 9 && buf[i] != '|' && buf[i] != 0){
++i;
}
while (i < 9 && buf[i] != '|' && buf[i] != 0){++i;}
unsigned int j = i;
while (j < 1024 && buf[j] != '\n' && buf[j] != 0){
++j;
}
while (j < 1024 && buf[j] != '\n' && buf[j] != 0){++j;}
buf[j] = 0;
if(i < 9){
if (i < 9){
buf[i] = 0;
Log(buf,buf+i+1);
Log(buf, buf + i + 1);
}else{
printf("%s", buf);
}
@ -83,9 +80,11 @@ namespace Controller {
/// Writes the current config to shared memory to be used in other processes
/// \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
/// 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(){
static JSON::Value writeConf;
bool changed = false;
@ -104,7 +103,7 @@ namespace Controller {
VERYHIGH_MSG("Saving new config because of edit in capabilities");
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);
if (!mistConfOut.mapped){
@ -112,70 +111,92 @@ namespace Controller {
return;
}
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
//lock semaphore
// lock semaphore
configLock.wait();
//write config
// write config
std::string temp = writeConf.toPacked();
memcpy(mistConfOut.mapped, temp.data(), std::min(temp.size(), (size_t)mistConfOut.len));
//unlock semaphore
// unlock semaphore
configLock.post();
/*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];
//for all shm pages that hold triggers
// for all shm pages that hold triggers
pageForType.clear();
if( writeConf["config"]["triggers"].size() ){//if triggers are defined...
jsonForEach(writeConf["config"]["triggers"], it){//for all types defined in config
snprintf(tmpBuf,NAME_BUFFER_SIZE,SHM_TRIGGER,(it.key()).c_str()); //create page
pageForType[it.key()].init(tmpBuf, 8*1024, true, false);// todo: should this be false/why??
char * bytePos=pageForType[it.key()].mapped;
if (writeConf["config"]["triggers"].size()){
jsonForEach(writeConf["config"]["triggers"], it){
snprintf(tmpBuf, NAME_BUFFER_SIZE, SHM_TRIGGER, (it.key()).c_str());
pageForType[it.key()].init(tmpBuf, 32 * 1024, true, false);
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
jsonForEach(*it, triggIt){ //for all defined
unsigned int tmpUrlSize=(*triggIt)[(unsigned int) 0].asStringRef().size();
unsigned int tmpStreamNames=0;// (*triggIt)[2ul].packedSize();
std::string namesArray="";
// write data to page
jsonForEach(*it, triggIt){
if (i >= max){
ERROR_MSG("Not all %s triggers fit on the memory page!", (it.key()).c_str());
break;
}
if( (triggIt->size() >= 3) && (*triggIt)[2ul].size()){
jsonForEach((*triggIt)[2ul], shIt){
unsigned int tmpLen=shIt->asString().size();
tmpStreamNames+= 4+tmpLen;
//INFO_MSG("adding string: %s len: %d", shIt->asString().c_str() , tmpLen );
((unsigned int*)tmpBuf)[0] = tmpLen; //NOTE: namesArray may be replaced by writing directly to tmpBuf.
namesArray.append(tmpBuf,4);
if (triggIt->isArray()){
tPage.setString("url", (*triggIt)[0u].asStringRef(), i);
tPage.setInt("sync", ((*triggIt)[1u].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->size() >= 3) && (*triggIt)[2u].size()){
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
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
continue;
}
((unsigned int*)bytePos)[0] = totalLen;
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;
if (triggIt->isObject()){
if (!triggIt->isMember("handler")){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);
}
}
++i;
}
tPage.setRCount(std::min(i, max));
}
}
static bool serverStartTriggered;
if(!serverStartTriggered){
if (!Triggers::doTrigger("SYSTEM_START")){
conf.is_active = false;
}
if (!serverStartTriggered){
if (!Triggers::doTrigger("SYSTEM_START")){conf.is_active = false;}
serverStartTriggered++;
}
if (Triggers::shouldTrigger("SYSTEM_CONFIG")){
@ -183,19 +204,6 @@ namespace Controller {
Triggers::doTrigger("SYSTEM_CONFIG", payload);
}
/*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
}
/*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
/// 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() {
static bool wentDry = false;
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 lastms = 0;
long long unsigned int fragCount = 0xFFFFull;
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()) {
continue;
}
@ -290,20 +301,31 @@ namespace Mist {
}
}
/*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;
fillBufferDetails(stream_details);
if (lastFragCount == 0xFFFFull) {
std::string payload = config->getString("streamname") + "\nFULL\n" + stream_details.toString();
Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname"));
std::string payload = streamName + "\nFULL\n" + stream_details.toString();
Triggers::doTrigger("STREAM_BUFFER", payload, streamName);
}else{
if (stream_details.isMember("issues") != wentDry){
if (stream_details.isMember("issues")){
std::string payload = config->getString("streamname") + "\nDRY\n" + stream_details.toString();
Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname"));
std::string payload = streamName + "\nDRY\n" + stream_details.toString();
Triggers::doTrigger("STREAM_BUFFER", payload, streamName);
}else{
std::string payload = config->getString("streamname") + "\nRECOVER\n" + stream_details.toString();
Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname"));
std::string payload = streamName + "\nRECOVER\n" + stream_details.toString();
Triggers::doTrigger("STREAM_BUFFER", payload, streamName);
}
}
}