Merge pull request #578 from Paulchen-Panther/api_auth

Token Management, Database, ...
This commit is contained in:
Paulchen Panther
2019-08-11 23:25:06 +02:00
committed by GitHub
138 changed files with 5879 additions and 2958 deletions

View File

@@ -12,11 +12,11 @@ add_subdirectory(protoserver)
add_subdirectory(bonjour)
add_subdirectory(ssdp)
add_subdirectory(boblightserver)
add_subdirectory(udplistener)
add_subdirectory(leddevice)
add_subdirectory(utils)
add_subdirectory(effectengine)
add_subdirectory(grabber)
add_subdirectory(webserver)
add_subdirectory(db)
add_subdirectory(api)
add_subdirectory(python)

View File

@@ -0,0 +1,44 @@
{
"type":"object",
"required":true,
"properties":{
"command": {
"type" : "string",
"required" : true,
"enum" : ["authorize"]
},
"subcommand" : {
"type" : "string",
"required" : true,
"enum" : ["requestToken","createToken","deleteToken","getTokenList","logout","login","required","answerRequest","getPendingRequests"]
},
"tan" : {
"type" : "integer"
},
"username": {
"type": "string",
"minLength" : 3
},
"password": {
"type": "string",
"minLength" : 8
},
"token": {
"type": "string",
"minLength" : 36
},
"comment" : {
"type" : "string",
"minLength" : 5
},
"id" : {
"type" : "string",
"minLength" : 5,
"maxLength" : 5
},
"accept" : {
"type" : "boolean"
}
},
"additionalProperties": false
}

View File

@@ -21,7 +21,7 @@
"component":
{
"type" : "string",
"enum" : ["ALL", "SMOOTHING", "BLACKBORDER", "FORWARDER", "UDPLISTENER", "BOBLIGHTSERVER", "GRABBER", "V4L", "LEDDEVICE"],
"enum" : ["ALL", "SMOOTHING", "BLACKBORDER", "FORWARDER", "BOBLIGHTSERVER", "GRABBER", "V4L", "LEDDEVICE"],
"required": true
},
"state":

View File

@@ -0,0 +1,29 @@
{
"type":"object",
"required":true,
"properties":{
"command": {
"type" : "string",
"required" : true,
"enum" : ["instance"]
},
"subcommand" : {
"type" : "string",
"required" : true,
"enum" : ["createInstance","deleteInstance","startInstance","stopInstance","saveName","switchTo"]
},
"tan" : {
"type" : "integer"
},
"instance" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"name": {
"type": "string",
"minLength" : 5
}
},
"additionalProperties": false
}

View File

@@ -5,7 +5,7 @@
"command": {
"type" : "string",
"required" : true,
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "transform", "correction" , "temperature"]
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "transform", "correction" , "temperature"]
}
}
}

View File

@@ -18,6 +18,8 @@
<file alias="schema-logging">JSONRPC_schema/schema-logging.json</file>
<file alias="schema-processing">JSONRPC_schema/schema-processing.json</file>
<file alias="schema-videomode">JSONRPC_schema/schema-videomode.json</file>
<file alias="schema-authorize">JSONRPC_schema/schema-authorize.json</file>
<file alias="schema-instance">JSONRPC_schema/schema-instance.json</file>
<!-- The following schemas are derecated but used to ensure backward compatibility with hyperion Classic remote control-->
<file alias="schema-transform">JSONRPC_schema/schema-hyperion-classic.json</file>
<file alias="schema-correction">JSONRPC_schema/schema-hyperion-classic.json</file>

View File

@@ -35,29 +35,94 @@
// api includes
#include <api/JsonCB.h>
// auth manager
#include <hyperion/AuthManager.h>
using namespace hyperion;
JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListener)
JsonAPI::JsonAPI(QString peerAddress, Logger* log, const bool& localConnection, QObject* parent, bool noListener)
: QObject(parent)
, _authManager(AuthManager::getInstance())
, _authorized(false)
, _userAuthorized(false)
, _apiAuthRequired(_authManager->isAuthRequired())
, _noListener(noListener)
, _peerAddress(peerAddress)
, _log(log)
, _hyperion(Hyperion::getInstance())
, _jsonCB(new JsonCB(this))
, _instanceManager(HyperionIManager::getInstance())
, _hyperion(nullptr)
, _jsonCB(nullptr)
, _streaming_logging_activated(false)
, _image_stream_timeout(0)
, _led_stream_timeout(0)
{
Q_INIT_RESOURCE(JSONRPC_schemas);
// the JsonCB creates json messages you can subscribe to e.g. data change events; forward them to the parent client
connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
// if this is localConnection and network allows unauth locals, set authorized flag
if(_apiAuthRequired && localConnection)
_authorized = !_authManager->isLocalAuthRequired();
// setup auth interface
connect(_authManager, &AuthManager::newPendingTokenRequest, this, &JsonAPI::handlePendingTokenRequest);
connect(_authManager, &AuthManager::tokenResponse, this, &JsonAPI::handleTokenResponse);
// listen for killed instances
connect(_instanceManager, &HyperionIManager::instanceStateChanged, this, &JsonAPI::handleInstanceStateChange);
// init Hyperion pointer
handleInstanceSwitch(0);
// notify hyperion about a jsonMessageForward
connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage);
}
void JsonAPI::handleMessage(const QString& messageString)
bool JsonAPI::handleInstanceSwitch(const quint8& inst, const bool& forced)
{
// check if we are already on the requested instance
if(_hyperion != nullptr && _hyperion->getInstanceIndex() == inst)
return true;
if(_instanceManager->IsInstanceRunning(inst))
{
Debug(_log,"Client '%s' switch to Hyperion instance %d", QSTRING_CSTR(_peerAddress), inst);
// cut all connections between hyperion / plugins and this
if(_hyperion != nullptr)
disconnect(_hyperion, 0, this, 0);
// get new Hyperion pointer
_hyperion = _instanceManager->getHyperionInstance(inst);
// the JsonCB creates json messages you can subscribe to e.g. data change events; forward them to the parent client
QStringList cbCmds;
if(_jsonCB != nullptr)
{
cbCmds = _jsonCB->getSubscribedCommands();
delete _jsonCB;
}
_jsonCB = new JsonCB(_hyperion, this);
connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
// read subs
for(const auto & entry : cbCmds)
{
_jsonCB->subscribeFor(entry);
}
// // imageStream last state
// if(_ledcolorsImageActive)
// connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection);
//
// //ledColor stream last state
// if(_ledcolorsLedsActive)
// connect(_hyperion, &Hyperion::rawLedColors, this, &JsonAPI::streamLedcolorsUpdate, Qt::UniqueConnection);
return true;
}
return false;
}
void JsonAPI::handleMessage(const QString& messageString, const QString& httpAuthHeader)
{
const QString ident = "JsonRpc@"+_peerAddress;
QJsonObject message;
@@ -84,6 +149,29 @@ void JsonAPI::handleMessage(const QString& messageString)
}
int tan = message["tan"].toInt();
// client auth before everything else but not for http
if (!_noListener && command == "authorize")
{
handleAuthorizeCommand(message, command, tan);
return;
}
// on the fly auth available for http from http Auth header, on failure we return and auth handler sends a failure
if(_noListener && _apiAuthRequired && !_authorized)
{
// extract token from http header
QString cToken = httpAuthHeader.mid(5).trimmed();
if(!handleHTTPAuth(command, tan, cToken))
return;
}
// on strong api auth you need a auth for all cmds
if(_apiAuthRequired && !_authorized)
{
sendErrorReply("No Authorization", command, tan);
return;
}
// switch over all possible commands and handle them
if (command == "color") handleColorCommand (message, command, tan);
@@ -102,15 +190,17 @@ void JsonAPI::handleMessage(const QString& messageString)
else if (command == "logging") handleLoggingCommand (message, command, tan);
else if (command == "processing") handleProcessingCommand (message, command, tan);
else if (command == "videomode") handleVideoModeCommand (message, command, tan);
else if (command == "instance") handleInstanceCommand (message, command, tan);
// BEGIN | The following commands are derecated but used to ensure backward compatibility with hyperion Classic remote control
else if (command == "clearall") handleClearallCommand(message, command, tan);
else if (command == "clearall")
handleClearallCommand(message, command, tan);
else if (command == "transform" || command == "correction" || command == "temperature")
sendErrorReply("The command " + command + "is deprecated, please use the Hyperion Web Interface to configure");
// END
// handle not implemented commands
else handleNotImplemented ();
else handleNotImplemented();
}
void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& command, const int tan)
@@ -120,35 +210,13 @@ void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& comm
// extract parameters
int priority = message["priority"].toInt();
int duration = message["duration"].toInt(-1);
QString origin = message["origin"].toString("Empty") + "@"+_peerAddress;
const QString origin = message["origin"].toString("Empty") + "@"+_peerAddress;
std::vector<ColorRgb> colorData(_hyperion->getLedCount());
const QJsonArray & jsonColor = message["color"].toArray();
unsigned int i = 0;
for (; i < unsigned(jsonColor.size()/3) && i < _hyperion->getLedCount(); ++i)
{
colorData[i].red = uint8_t(jsonColor.at(3u*i).toInt());
colorData[i].green = uint8_t(jsonColor.at(3u*i+1u).toInt());
colorData[i].blue = uint8_t(jsonColor.at(3u*i+2u).toInt());
}
const ColorRgb color = {uint8_t(jsonColor.at(0).toInt()),uint8_t(jsonColor.at(1).toInt()),uint8_t(jsonColor.at(2).toInt())};
// copy full blocks of led colors
unsigned size = i;
while (i + size < _hyperion->getLedCount())
{
memcpy(&(colorData[i]), colorData.data(), size * sizeof(ColorRgb));
i += size;
}
// copy remaining block of led colors
if (i < _hyperion->getLedCount())
{
memcpy(&(colorData[i]), colorData.data(), (_hyperion->getLedCount()-i) * sizeof(ColorRgb));
}
// register and set color
_hyperion->registerInput(priority, hyperion::COMP_COLOR, origin);
_hyperion->setInput(priority, colorData, duration);
// set color
_hyperion->setColor(priority, color, duration, origin);
// send reply
sendSuccessReply(command, tan);
@@ -251,7 +319,7 @@ void JsonAPI::handleSysInfoCommand(const QJsonObject&, const QString& command, c
hyperion["channel" ] = QString(HYPERION_VERSION_CHANNEL);
hyperion["build" ] = QString(HYPERION_BUILD_ID);
hyperion["time" ] = QString(__DATE__ " " __TIME__);
hyperion["id" ] = _hyperion->getId();
hyperion["id" ] = _authManager->getID();
info["hyperion"] = hyperion;
// send the result
@@ -446,14 +514,6 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString&
info["components"] = component;
info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType());
// Add Hyperion
QJsonObject hyperion;
hyperion["config_modified" ] = _hyperion->configModified();
hyperion["config_writeable"] = _hyperion->configWriteable();
hyperion["enabled"] = _hyperion->getComponentRegister().isComponentEnabled(hyperion::COMP_ALL) ? true : false;
info["hyperion"] = hyperion;
// add sessions
QJsonArray sessions;
for (auto session: BonjourBrowserWrapper::getInstance()->getAllServices())
@@ -470,6 +530,22 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString&
}
info["sessions"] = sessions;
// add instance info
QJsonArray instanceInfo;
for(const auto & entry : _instanceManager->getInstanceData())
{
QJsonObject obj;
obj.insert("friendly_name", entry["friendly_name"].toString());
obj.insert("instance", entry["instance"].toInt());
//obj.insert("last_use", entry["last_use"].toString());
obj.insert("running", entry["running"].toBool());
instanceInfo.append(obj);
}
info["instance"] = instanceInfo;
// add leds configs
info["leds"] = _hyperion->getSetting(settings::LEDS).array();
// BEGIN | The following entries are derecated but used to ensure backward compatibility with hyperion Classic remote control
// TODO Output the real transformation information instead of default
@@ -588,7 +664,8 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString&
}
for(const auto & entry : subsArr)
{
if(entry == "settings-update")
// config callbacks just if auth is set
if(entry == "settings-update" && !_authorized)
continue;
if(!_jsonCB->subscribeFor(entry.toString()))
@@ -617,6 +694,17 @@ void JsonAPI::handleClearCommand(const QJsonObject& message, const QString& comm
sendSuccessReply(command, tan);
}
void JsonAPI::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan)
{
emit forwardJsonMessage(message);
// clear priority
_hyperion->clearall();
// send reply
sendSuccessReply(command, tan);
}
void JsonAPI::handleAdjustmentCommand(const QJsonObject& message, const QString& command, const int tan)
{
const QJsonObject & adjustment = message["adjustment"].toObject();
@@ -848,8 +936,6 @@ void JsonAPI::handleComponentStateCommand(const QJsonObject& message, const QStr
{
if(_hyperion->getComponentRegister().setHyperionEnable(compState))
sendSuccessReply(command, tan);
else
sendErrorReply(QString("Hyperion is already %1").arg(compState ? "enabled" : "disabled"), command, tan );
return;
}
@@ -885,6 +971,7 @@ void JsonAPI::handleLedColorsCommand(const QJsonObject& message, const QString &
_streaming_image_reply["command"] = command+"-imagestream-update";
_streaming_image_reply["tan"] = tan;
connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection);
_hyperion->update();
}
else if (subcommand == "imagestream-stop")
{
@@ -892,7 +979,6 @@ void JsonAPI::handleLedColorsCommand(const QJsonObject& message, const QString &
}
else
{
sendErrorReply("unknown subcommand \""+subcommand+"\"",command,tan);
return;
}
@@ -928,7 +1014,6 @@ void JsonAPI::handleLoggingCommand(const QJsonObject& message, const QString &co
}
else
{
sendErrorReply("unknown subcommand",command,tan);
return;
}
@@ -949,15 +1034,282 @@ void JsonAPI::handleVideoModeCommand(const QJsonObject& message, const QString &
sendSuccessReply(command, tan);
}
void JsonAPI::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan)
void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString &command, const int tan)
{
emit forwardJsonMessage(message);
const QString& subc = message["subcommand"].toString().trimmed();
// catch test if auth is required
if(subc == "required")
{
QJsonObject req;
req["required"] = _apiAuthRequired;
sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan);
return;
}
// clear priority
_hyperion->clearall();
// catch logout
if(subc == "logout")
{
_authorized = false;
_userAuthorized = false;
sendSuccessReply(command+"-"+subc, tan);
return;
}
// send reply
sendSuccessReply(command, tan);
// token created from ui
if(subc == "createToken")
{
const QString& c = message["comment"].toString().trimmed();
// for user authorized sessions
if(_userAuthorized)
{
AuthManager::AuthDefinition def = _authManager->createToken(c);
QJsonObject newTok;
newTok["comment"] = def.comment;
newTok["id"] = def.id;
newTok["token"] = def.token;
sendSuccessDataReply(QJsonDocument(newTok), command+"-"+subc, tan);
return;
}
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// delete token
if(subc == "deleteToken")
{
const QString& did = message["id"].toString().trimmed();
// for user authorized sessions
if(_userAuthorized)
{
_authManager->deleteToken(did);
sendSuccessReply(command+"-"+subc, tan);
return;
}
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// catch token request
if(subc == "requestToken")
{
const QString& comment = message["comment"].toString().trimmed();
const QString& id = message["id"].toString().trimmed();
_authManager->setNewTokenRequest(this, comment, id);
// client should wait for answer
return;
}
// get pending token requests
if(subc == "getPendingRequests")
{
if(_userAuthorized)
{
QMap<QString, AuthManager::AuthDefinition> map = _authManager->getPendingRequests();
QJsonArray arr;
for(const auto& entry : map)
{
QJsonObject obj;
obj["comment"] = entry.comment;
obj["id"] = entry.id;
obj["timeout"] = int(entry.timeoutTime - QDateTime::currentMSecsSinceEpoch());
arr.append(obj);
}
sendSuccessDataReply(QJsonDocument(arr),command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
return;
}
// accept/deny token request
if(subc == "answerRequest")
{
const QString& id = message["id"].toString().trimmed();
const bool& accept = message["accept"].toBool(false);
if(_userAuthorized)
{
if(accept)
_authManager->acceptTokenRequest(id);
else
_authManager->denyTokenRequest(id);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
return;
}
// deny token request
if(subc == "acceptRequest")
{
const QString& id = message["id"].toString().trimmed();
if(_userAuthorized)
{
_authManager->acceptTokenRequest(id);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
return;
}
// cath get token list
if(subc == "getTokenList")
{
if(_userAuthorized)
{
QVector<AuthManager::AuthDefinition> defVect = _authManager->getTokenList();
QJsonArray tArr;
for(const auto& entry : defVect)
{
QJsonObject subO;
subO["comment"] = entry.comment;
subO["id"] = entry.id;
subO["last_use"] = entry.lastUse;
tArr.append(subO);
}
sendSuccessDataReply(QJsonDocument(tArr),command+"-"+subc, tan);
return;
}
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// login
if(subc == "login")
{
// catch token auth
const QString& token = message["token"].toString().trimmed();
if(!token.isEmpty())
{
if(token.count() >= 36)
{
if(_authManager->isTokenAuthorized(token))
{
_authorized = true;
sendSuccessReply(command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
}
else
sendErrorReply("Token is too short", command+"-"+subc, tan);
return;
}
// user & password
const QString& user = message["username"].toString().trimmed();
const QString& password = message["password"].toString().trimmed();
if(user.count() >= 3 && password.count() >= 8)
{
if(_authManager->isUserAuthorized(user, password))
{
_authorized = true;
_userAuthorized = true;
sendSuccessReply(command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
}
else
sendErrorReply("User or password string too short", command+"-"+subc, tan);
}
}
bool JsonAPI::handleHTTPAuth(const QString& command, const int& tan, const QString& token)
{
if(_authManager->isTokenAuthorized(token))
{
_authorized = true;
return true;
}
sendErrorReply("No Authorization", command, tan);
return false;
}
void JsonAPI::handleInstanceCommand(const QJsonObject & message, const QString &command, const int tan)
{
const QString & subc = message["subcommand"].toString();
const quint8 & inst = message["instance"].toInt();
const QString & name = message["name"].toString();
if(subc == "switchTo")
{
if(handleInstanceSwitch(inst))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply("Selected Hyperion instance isn't running",command+"-"+subc, tan);
return;
}
if(subc == "startInstance")
{
// silent fail
_instanceManager->startInstance(inst);
sendSuccessReply(command+"-"+subc, tan);
return;
}
if(subc == "stopInstance")
{
// silent fail
_instanceManager->stopInstance(inst);
sendSuccessReply(command+"-"+subc, tan);
return;
}
if(subc == "deleteInstance")
{
if(_userAuthorized)
{
if(_instanceManager->deleteInstance(inst))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply(QString("Failed to delete instance '%1'").arg(inst), command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// create and save name requires name
if(name.isEmpty())
sendErrorReply("Name string required for this command",command+"-"+subc, tan);
if(subc == "createInstance")
{
if(_userAuthorized)
{
if(_instanceManager->createInstance(name))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply(QString("The instance name '%1' is already in use").arg(name), command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
if(subc == "saveName")
{
if(_userAuthorized)
{
// silent fail
if(_instanceManager->saveName(inst,name))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply(QString("The instance name '%1' is already in use").arg(name), command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
}
void JsonAPI::handleNotImplemented()
@@ -1093,3 +1445,49 @@ void JsonAPI::incommingLogMessage(const Logger::T_LOG_MESSAGE &msg)
// send the result
emit callbackMessage(_streaming_logging_reply);
}
void JsonAPI::handlePendingTokenRequest(const QString& id, const QString& comment)
{
// just user sessions are allowed to react on this, to prevent that token authorized instances authorize new tokens on their own
if(_userAuthorized)
{
QJsonObject obj;
obj["command"] = "authorize-event";
obj["comment"] = comment;
obj["id"] = id;
emit callbackMessage(obj);
}
}
void JsonAPI::handleTokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id)
{
// if this is the requester, we send the reply
if(this == caller)
{
const QString cmd = "authorize-requestToken";
QJsonObject result;
result["token"] = token;
result["comment"] = comment;
result["id"] = id;
if(success)
sendSuccessDataReply(QJsonDocument(result), cmd);
else
sendErrorReply("Token request timeout or denied", cmd);
}
}
void JsonAPI::handleInstanceStateChange(const instanceState& state, const quint8& instance, const QString& name)
{
switch(state){
case H_ON_STOP:
if(_hyperion->getInstanceIndex() == instance)
{
handleInstanceSwitch();
}
break;
default:
break;
}
}

View File

@@ -3,13 +3,23 @@
// hyperion
#include <hyperion/Hyperion.h>
// HyperionIManager
#include <hyperion/HyperionIManager.h>
// components
#include <hyperion/ComponentRegister.h>
// bonjour wrapper
#include <bonjour/bonjourbrowserwrapper.h>
// priorityMuxer
#include <hyperion/PriorityMuxer.h>
// utils
#include <utils/ColorSys.h>
// qt
#include <QDateTime>
// Image to led map helper
@@ -17,15 +27,15 @@
using namespace hyperion;
JsonCB::JsonCB(QObject* parent)
JsonCB::JsonCB(Hyperion* hyperion, QObject* parent)
: QObject(parent)
, _hyperion(Hyperion::getInstance())
, _hyperion(hyperion)
, _componentRegister(& _hyperion->getComponentRegister())
, _bonjour(BonjourBrowserWrapper::getInstance())
, _prioMuxer(_hyperion->getMuxerInstance())
{
_availableCommands << "components-update" << "sessions-update" << "priorities-update" << "imageToLedMapping-update"
<< "adjustment-update" << "videomode-update" << "effects-update" << "settings-update";
<< "adjustment-update" << "videomode-update" << "effects-update" << "settings-update" << "leds-update" << "instance-update";
}
bool JsonCB::subscribeFor(const QString& type)
@@ -82,6 +92,19 @@ bool JsonCB::subscribeFor(const QString& type)
connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange, Qt::UniqueConnection);
}
if(type == "leds-update")
{
_subscribedCommands << type;
connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange, Qt::UniqueConnection);
}
if(type == "instance-update")
{
_subscribedCommands << type;
connect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange, Qt::UniqueConnection);
}
return true;
}
@@ -301,3 +324,29 @@ void JsonCB::handleSettingsChange(const settings::type& type, const QJsonDocumen
doCallback("settings-update", QVariant(dat));
}
void JsonCB::handleLedsConfigChange(const settings::type& type, const QJsonDocument& data)
{
if(type == settings::LEDS)
{
QJsonObject dat;
dat[typeToString(type)] = data.array();
doCallback("leds-update", QVariant(dat));
}
}
void JsonCB::handleInstanceChange()
{
QJsonArray arr;
for(const auto & entry : HyperionIManager::getInstance()->getInstanceData())
{
QJsonObject obj;
obj.insert("friendly_name", entry["friendly_name"].toString());
obj.insert("instance", entry["instance"].toInt());
//obj.insert("last_use", entry["last_use"].toString());
obj.insert("running", entry["running"].toBool());
arr.append(obj);
}
doCallback("instance-update", QVariant(arr));
}

View File

@@ -44,12 +44,9 @@ BoblightClientConnection::BoblightClientConnection(Hyperion* hyperion, QTcpSocke
BoblightClientConnection::~BoblightClientConnection()
{
if (_priority < 255)
{
// clear the current channel
// clear the current channel
if (_priority != 0 && _priority >= 128 && _priority < 254)
_hyperion->clear(_priority);
_priority = 255;
}
delete _socket;
}
@@ -83,12 +80,9 @@ void BoblightClientConnection::readData()
void BoblightClientConnection::socketClosed()
{
if (_priority < 255)
{
// clear the current channel
// clear the current channel
if (_priority != 0 && _priority >= 128 && _priority < 254)
_hyperion->clear(_priority);
_priority = 255;
}
emit connectionClosed(this);
}
@@ -163,8 +157,11 @@ void BoblightClientConnection::handleMessage(const QString & message)
rgb.green = green;
rgb.blue = blue;
if (_priority == 0 || _priority < 128 || _priority >= 254)
return;
// send current color values to hyperion if this is the last led assuming leds values are send in order of id
if ((ledIndex == _ledColors.size() -1) && _priority < 255)
if (ledIndex == _ledColors.size() -1)
{
_hyperion->setInput(_priority, _ledColors);
}
@@ -188,27 +185,38 @@ void BoblightClientConnection::handleMessage(const QString & message)
int prio = messageParts[2].toInt(&rc);
if (rc && prio != _priority)
{
if (_priority < 255)
{
// clear the current channel
if (_priority != 0 && _hyperion->getPriorityInfo(_priority).componentId == hyperion::COMP_BOBLIGHTSERVER)
_hyperion->clear(_priority);
if (prio < 128 || prio >= 254)
{
_priority = 128;
while (_hyperion->getActivePriorities().contains(_priority))
{
_priority += 1;
}
// warn against invalid priority
Warning(_log, "The priority %i is not in the priority range between 128 and 253. Priority %i is used instead.", prio, _priority);
// register new priority (previously modified)
_hyperion->registerInput(_priority, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(_socket->peerAddress().toString()));
}
else
{
// register new priority
_hyperion->registerInput(prio, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(_socket->peerAddress().toString()));
_priority = prio;
}
_priority = prio;
return;
}
}
}
else if (messageParts[0] == "sync")
{
// send current color values to hyperion
if (_priority < 255)
{
_hyperion->setInput(_priority, _ledColors);
}
if (_priority != 0 && _priority >= 128 && _priority < 254)
_hyperion->setInput(_priority, _ledColors); // send current color values to hyperion
return;
}
}
@@ -216,12 +224,6 @@ void BoblightClientConnection::handleMessage(const QString & message)
Debug(_log, "unknown boblight message: %s", QSTRING_CSTR(message));
}
void BoblightClientConnection::sendMessage(const QByteArray & message)
{
//std::cout << "send boblight message: " << message;
_socket->write(message);
}
void BoblightClientConnection::sendLightMessage()
{
char buffer[256];

View File

@@ -63,7 +63,7 @@ private:
///
/// @param message The boblight message to send
///
void sendMessage(const QByteArray &message);
void sendMessage(const QByteArray &message) { _socket->write(message); };
///
/// Send a lights message the to connected client

View File

@@ -7,9 +7,13 @@
// hyperion includes
#include <hyperion/Hyperion.h>
// qt incl
#include <QTcpServer>
// netUtil
#include <utils/NetUtils.h>
using namespace hyperion;
BoblightServer::BoblightServer(Hyperion* hyperion,const QJsonDocument& config)
@@ -42,11 +46,9 @@ void BoblightServer::start()
if ( _server->isListening() )
return;
if (!_server->listen(QHostAddress::Any, _port))
{
Error(_log, "Could not bind to port '%d', please use an available port", _port);
return;
}
if (NetUtils::portAvailable(_port, _log))
_server->listen(QHostAddress::Any, _port);
Info(_log, "Started on port %d", _port);
_hyperion->getComponentRegister().componentStateChanged(COMP_BOBLIGHTSERVER, _server->isListening());

View File

@@ -34,7 +34,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <utils/Logger.h>
#include <HyperionConfig.h>
#include <hyperion/Hyperion.h>
#include <hyperion/AuthManager.h>
BonjourServiceRegister::BonjourServiceRegister(QObject *parent)
: QObject(parent), dnssref(0), bonjourSocket(0)
@@ -79,7 +79,7 @@ void BonjourServiceRegister::registerService(const BonjourRecord &record, quint1
}
#endif
// base txtRec
std::vector<std::pair<std::string, std::string> > txtBase = {{"id",Hyperion::getInstance()->getId().toStdString()},{"version",HYPERION_VERSION}};
std::vector<std::pair<std::string, std::string> > txtBase = {{"id",AuthManager::getInstance()->getID().toStdString()},{"version",HYPERION_VERSION}};
// create txt record
TXTRecordRef txtRec;
TXTRecordCreate(&txtRec,0,NULL);

18
libsrc/db/CMakeLists.txt Normal file
View File

@@ -0,0 +1,18 @@
find_package(Qt5 COMPONENTS Sql REQUIRED)
# Define the current source locations
SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/db)
SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/db)
FILE ( GLOB DB_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
add_library(database
${DB_SOURCES}
)
target_link_libraries(database
hyperion
hyperion-utils
Qt5::Core
Qt5::Sql
)

396
libsrc/db/DBManager.cpp Normal file
View File

@@ -0,0 +1,396 @@
#include <db/DBManager.h>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QThreadStorage>
#include <QUuid>
#include <QDir>
// not in header because of linking
static QString _rootPath;
static QThreadStorage<QSqlDatabase> _databasePool;
DBManager::DBManager(QObject* parent)
: QObject(parent)
, _log(Logger::getInstance("DB"))
{
}
DBManager::~DBManager()
{
}
void DBManager::setRootPath(const QString& rootPath)
{
_rootPath = rootPath;
// create directory
QDir().mkpath(_rootPath+"/db");
}
void DBManager::setTable(const QString& table)
{
_table = table;
}
QSqlDatabase DBManager::getDB() const
{
if(_databasePool.hasLocalData())
return _databasePool.localData();
else
{
auto db = QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString());
_databasePool.setLocalData(db);
db.setDatabaseName(_rootPath+"/db/"+_dbn+".db");
if(!db.open())
{
Error(_log, QSTRING_CSTR(db.lastError().text()));
throw std::runtime_error("Failed to open database connection!");
}
return db;
}
}
bool DBManager::createRecord(const VectorPair& conditions, const QVariantMap& columns) const
{
if(recordExists(conditions))
{
// if there is no column data, return
if(columns.isEmpty())
return true;
if(!updateRecord(conditions, columns))
return false;
return true;
}
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
query.setForwardOnly(true);
QVariantList cValues;
QStringList prep;
QStringList placeh;
// prep merge columns & condition
QVariantMap::const_iterator i = columns.constBegin();
while (i != columns.constEnd()) {
prep.append(i.key());
cValues += i.value();
placeh.append("?");
++i;
}
for(const auto& pair : conditions)
{
// remove the condition statements
QString tmp = pair.first;
prep << tmp.remove("AND");
cValues << pair.second;
placeh.append("?");
}
query.prepare(QString("INSERT INTO %1 ( %2 ) VALUES ( %3 )").arg(_table,prep.join(", ")).arg(placeh.join(", ")));
// add column & condition values
doAddBindValue(query, cValues);
if(!query.exec())
{
Error(_log, "Failed to create record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prep.join(", ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
return true;
}
bool DBManager::recordExists(const VectorPair& conditions) const
{
if(conditions.isEmpty())
return false;
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
query.setForwardOnly(true);
QStringList prepCond;
QVariantList bindVal;
prepCond << "WHERE";
for(const auto& pair : conditions)
{
prepCond << pair.first+"=?";
bindVal << pair.second;
}
query.prepare(QString("SELECT * FROM %1 %2").arg(_table,prepCond.join(" ")));
doAddBindValue(query, bindVal);
if(!query.exec())
{
Error(_log, "Failed recordExists(): '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
int entry = 0;
while (query.next()) {
entry++;
}
if(entry)
return true;
return false;
}
bool DBManager::updateRecord(const VectorPair& conditions, const QVariantMap& columns) const
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
query.setForwardOnly(true);
QVariantList values;
QStringList prep;
// prepare columns valus
QVariantMap::const_iterator i = columns.constBegin();
while (i != columns.constEnd()) {
prep += i.key()+"=?";
values += i.value();
++i;
}
// prepare condition values
QStringList prepCond;
QVariantList prepBindVal;
if(!conditions.isEmpty())
prepCond << "WHERE";
for(const auto& pair : conditions)
{
prepCond << pair.first+"=?";
prepBindVal << pair.second;
}
query.prepare(QString("UPDATE %1 SET %2 %3").arg(_table,prep.join(", ")).arg(prepCond.join(" ")));
// add column values
doAddBindValue(query, values);
// add condition values
doAddBindValue(query, prepBindVal);
if(!query.exec())
{
Error(_log, "Failed to update record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
return true;
}
bool DBManager::getRecord(const VectorPair& conditions, QVariantMap& results, const QStringList& tColumns) const
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
query.setForwardOnly(true);
QString sColumns("*");
if(!tColumns.isEmpty())
sColumns = tColumns.join(", ");
// prep conditions
QStringList prepCond;
QVariantList bindVal;
if(!conditions.isEmpty())
prepCond << "WHERE";
for(const auto& pair : conditions)
{
prepCond << pair.first+"=?";
bindVal << pair.second;
}
query.prepare(QString("SELECT %1 FROM %2 %3").arg(sColumns,_table).arg(prepCond.join(" ")));
doAddBindValue(query, bindVal);
if(!query.exec())
{
Error(_log, "Failed to get record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
// go to first row
query.next();
QSqlRecord rec = query.record();
for(int i = 0; i<rec.count(); i++)
{
results[rec.fieldName(i)] = rec.value(i);
}
return true;
}
bool DBManager::getRecords(QVector<QVariantMap>& results, const QStringList& tColumns) const
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
query.setForwardOnly(true);
QString sColumns("*");
if(!tColumns.isEmpty())
sColumns = tColumns.join(", ");
query.prepare(QString("SELECT %1 FROM %2").arg(sColumns,_table));
if(!query.exec())
{
Error(_log, "Failed to get records: '%s' in table: '%s' Error: %s", QSTRING_CSTR(sColumns), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
// iterate through all found records
while(query.next())
{
QVariantMap entry;
QSqlRecord rec = query.record();
for(int i = 0; i<rec.count(); i++)
{
entry[rec.fieldName(i)] = rec.value(i);
}
results.append(entry);
}
return true;
}
bool DBManager::deleteRecord(const VectorPair& conditions) const
{
if(conditions.isEmpty())
{
Error(_log, "Oops, a deleteRecord() call wants to delete the entire table (%s)! Denied it", QSTRING_CSTR(_table));
return false;
}
if(recordExists(conditions))
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
// prep conditions
QStringList prepCond("WHERE");
QVariantList bindValues;
for(const auto& pair : conditions)
{
prepCond << pair.first+"=?";
bindValues << pair.second;
}
query.prepare(QString("DELETE FROM %1 %2").arg(_table,prepCond.join(" ")));
doAddBindValue(query, bindValues);
if(!query.exec())
{
Error(_log, "Failed to delete record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
return true;
}
return false;
}
bool DBManager::createTable(QStringList& columns) const
{
if(columns.isEmpty())
{
Error(_log,"Empty tables aren't supported!");
return false;
}
QSqlDatabase idb = getDB();
// create table if required
QSqlQuery query(idb);
if(!tableExists(_table))
{
// empty tables aren't supported by sqlite, add one column
QString tcolumn = columns.takeFirst();
// default CURRENT_TIMESTAMP is not supported by ALTER TABLE
if(!query.exec(QString("CREATE TABLE %1 ( %2 )").arg(_table,tcolumn)))
{
Error(_log, "Failed to create table: '%s' Error: %s", QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
}
// create columns if required
QSqlRecord rec = idb.record(_table);
int err = 0;
for(const auto& column : columns)
{
QStringList id = column.split(' ');
if(rec.indexOf(id.at(0)) == -1)
{
if(!createColumn(column))
{
err++;
}
}
}
if(err)
return false;
return true;
}
bool DBManager::createColumn(const QString& column) const
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
if(!query.exec(QString("ALTER TABLE %1 ADD COLUMN %2").arg(_table,column)))
{
Error(_log, "Failed to create column: '%s' in table: '%s' Error: %s", QSTRING_CSTR(column), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
return true;
}
bool DBManager::tableExists(const QString& table) const
{
QSqlDatabase idb = getDB();
QStringList tables = idb.tables();
if(tables.contains(table))
return true;
return false;
}
bool DBManager::deleteTable(const QString& table) const
{
if(tableExists(table))
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
if(!query.exec(QString("DROP TABLE %1").arg(table)))
{
Error(_log, "Failed to delete table: '%s' Error: %s", QSTRING_CSTR(table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
}
return true;
}
void DBManager::doAddBindValue(QSqlQuery& query, const QVariantList& variants) const
{
for(const auto& variant : variants)
{
QVariant::Type t = variant.type();
switch(t)
{
case QVariant::UInt:
case QVariant::Int:
case QVariant::Bool:
query.addBindValue(variant.toInt());
break;
case QVariant::Double:
query.addBindValue(variant.toFloat());
break;
case QVariant::ByteArray:
query.addBindValue(variant.toByteArray());
break;
default:
query.addBindValue(variant.toString());
break;
}
}
}

View File

@@ -79,7 +79,7 @@ void Effect::run()
PyObject * module = PyImport_ImportModule("hyperion");
// add a capsule containing 'this' to the module to be able to retrieve the effect from the callback function
PyObject_SetAttrString(module, "__effectObj", PyCapsule_New(this, nullptr, nullptr));
PyModule_AddObject(module, "__effectObj", PyCapsule_New((void*)this, "hyperion.__effectObj", nullptr));
// add ledCount variable to the interpreter
PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _hyperion->getLedCount()));

View File

@@ -168,10 +168,11 @@ int EffectEngine::runEffectScript(const QString &script, const QString &name, co
channelCleared(priority);
// create the effect
Effect *effect = new Effect(_hyperion, priority, timeout, script, name, args, imageData);
Effect *effect = new Effect(_hyperion, priority, timeout, script, name, args, imageData);
connect(effect, &Effect::setInput, _hyperion, &Hyperion::setInput, Qt::QueuedConnection);
connect(effect, &Effect::setInputImage, _hyperion, &Hyperion::setInputImage, Qt::QueuedConnection);
connect(effect, &QThread::finished, this, &EffectEngine::effectFinished);
connect(_hyperion, &Hyperion::finished, effect, &Effect::requestInterruption, Qt::DirectConnection);
_activeEffects.push_back(effect);
// start the effect
@@ -185,9 +186,9 @@ void EffectEngine::channelCleared(int priority)
{
for (Effect * effect : _activeEffects)
{
if (effect->getPriority() == priority)
if (effect->getPriority() == priority && !effect->isInterruptionRequested())
{
effect->setInteruptionFlag();
effect->requestInterruption();
}
}
}
@@ -196,9 +197,9 @@ void EffectEngine::allChannelsCleared()
{
for (Effect * effect : _activeEffects)
{
if (effect->getPriority() != 254)
if (effect->getPriority() != 254 && !effect->isInterruptionRequested())
{
effect->setInteruptionFlag();
effect->requestInterruption();
}
}
}
@@ -206,7 +207,7 @@ void EffectEngine::allChannelsCleared()
void EffectEngine::effectFinished()
{
Effect* effect = qobject_cast<Effect*>(sender());
if (!effect->hasInteruptionFlag())
if (!effect->isInterruptionRequested())
{
// effect stopped by itself. Clear the channel
_hyperion->clear(effect->getPriority());

View File

@@ -13,6 +13,9 @@
#include <QImageReader>
#include <QBuffer>
// Get the effect from the capsule
#define getEffect() static_cast<Effect*>((Effect*)PyCapsule_Import("hyperion.__effectObj", 0))
// create the hyperion module
struct PyModuleDef EffectModule::moduleDef = {
PyModuleDef_HEAD_INIT,
@@ -41,9 +44,9 @@ PyObject *EffectModule::json2python(const QJsonValue &jsonData)
switch (jsonData.type())
{
case QJsonValue::Null:
return Py_BuildValue("");
Py_RETURN_NONE;
case QJsonValue::Undefined:
return Py_BuildValue("");
Py_RETURN_NOTIMPLEMENTED;
case QJsonValue::Double:
{
if (std::round(jsonData.toDouble()) != jsonData.toDouble())
@@ -84,7 +87,7 @@ PyObject *EffectModule::json2python(const QJsonValue &jsonData)
}
assert(false);
return nullptr;
Py_RETURN_NONE;
}
// Python method table
@@ -118,26 +121,17 @@ PyMethodDef EffectModule::effectMethods[] = {
PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args)
{
// get the effect
Effect * effect = getEffect();
// check if we have aborted already
if (effect->hasInteruptionFlag())
{
return Py_BuildValue("");
}
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
// determine the timeout
int timeout = effect->_timeout;
int timeout = getEffect()->_timeout;
if (timeout > 0)
{
timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch();
timeout = getEffect()->_endTime - QDateTime::currentMSecsSinceEpoch();
// we are done if the time has passed
if (timeout <= 0)
{
return Py_BuildValue("");
}
if (timeout <= 0) Py_RETURN_NONE;
}
// check the number of arguments
@@ -148,9 +142,9 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args)
ColorRgb color;
if (PyArg_ParseTuple(args, "bbb", &color.red, &color.green, &color.blue))
{
effect->_colors.fill(color);
effect->setInput(effect->_priority, effect->_colors.toStdVector(), timeout, false);
return Py_BuildValue("");
getEffect()->_colors.fill(color);
getEffect()->setInput(getEffect()->_priority, getEffect()->_colors.toStdVector(), timeout, false);
Py_RETURN_NONE;
}
return nullptr;
}
@@ -163,12 +157,12 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args)
if (PyByteArray_Check(bytearray))
{
size_t length = PyByteArray_Size(bytearray);
if (length == 3 * effect->_hyperion->getLedCount())
if (length == 3 * getEffect()->_hyperion->getLedCount())
{
char * data = PyByteArray_AS_STRING(bytearray);
memcpy(effect->_colors.data(), data, length);
effect->setInput(effect->_priority, effect->_colors.toStdVector(), timeout, false);
return Py_BuildValue("");
memcpy(getEffect()->_colors.data(), data, length);
getEffect()->setInput(getEffect()->_priority, getEffect()->_colors.toStdVector(), timeout, false);
Py_RETURN_NONE;
}
else
{
@@ -196,26 +190,17 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args)
{
// get the effect
Effect * effect = getEffect();
// check if we have aborted already
if (effect->hasInteruptionFlag())
{
return Py_BuildValue("");
}
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
// determine the timeout
int timeout = effect->_timeout;
int timeout = getEffect()->_timeout;
if (timeout > 0)
{
timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch();
timeout = getEffect()->_endTime - QDateTime::currentMSecsSinceEpoch();
// we are done if the time has passed
if (timeout <= 0)
{
return Py_BuildValue("");
}
if (timeout <= 0) Py_RETURN_NONE;
}
// bytearray of values
@@ -231,8 +216,8 @@ PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args)
Image<ColorRgb> image(width, height);
char * data = PyByteArray_AS_STRING(bytearray);
memcpy(image.memptr(), data, length);
effect->setInputImage(effect->_priority, image, timeout, false);
return Py_BuildValue("");
getEffect()->setInputImage(getEffect()->_priority, image, timeout, false);
Py_RETURN_NONE;
}
else
{
@@ -258,13 +243,14 @@ PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
{
Effect *effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
QString file;
QBuffer buffer;
QImageReader reader;
if (effect->_imageData.isEmpty())
if (getEffect()->_imageData.isEmpty())
{
Q_INIT_RESOURCE(EffectEngine);
@@ -272,7 +258,7 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
if(!PyArg_ParseTuple(args, "s", &source))
{
PyErr_SetString(PyExc_TypeError, "String required");
return NULL;
return nullptr;
}
file = QString::fromUtf8(source);
@@ -285,7 +271,7 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
}
else
{
buffer.setData(QByteArray::fromBase64(effect->_imageData.toUtf8()));
buffer.setData(QByteArray::fromBase64(getEffect()->_imageData.toUtf8()));
buffer.open(QBuffer::ReadOnly);
reader.setDecideFormatFromContent(true);
reader.setDevice(&buffer);
@@ -321,7 +307,7 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
else
{
PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData());
return NULL;
return nullptr;
}
}
return result;
@@ -329,39 +315,29 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
else
{
PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData());
return NULL;
return nullptr;
}
}
PyObject* EffectModule::wrapAbort(PyObject *self, PyObject *)
{
Effect * effect = getEffect();
// Test if the effect has reached it end time
if (effect->_timeout > 0 && QDateTime::currentMSecsSinceEpoch() > effect->_endTime)
{
effect->setInteruptionFlag();
}
return Py_BuildValue("i", effect->hasInteruptionFlag() ? 1 : 0);
return Py_BuildValue("i", getEffect()->isInterruptionRequested() ? 1 : 0);
}
PyObject* EffectModule::wrapImageShow(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
// determine the timeout
int timeout = effect->_timeout;
int timeout = getEffect()->_timeout;
if (timeout > 0)
{
timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch();
timeout = getEffect()->_endTime - QDateTime::currentMSecsSinceEpoch();
// we are done if the time has passed
if (timeout <= 0)
{
return Py_BuildValue("");
}
if (timeout <= 0) Py_RETURN_NONE;
}
int argCount = PyTuple_Size(args);
@@ -372,13 +348,13 @@ PyObject* EffectModule::wrapImageShow(PyObject *self, PyObject *args)
argsOk = true;
}
if ( ! argsOk || (imgId>-1 && imgId >= effect->_imageStack.size()))
if ( ! argsOk || (imgId>-1 && imgId >= getEffect()->_imageStack.size()))
{
return nullptr;
}
QImage * qimage = (imgId<0) ? &(effect->_image) : &(effect->_imageStack[imgId]);
QImage * qimage = (imgId<0) ? &(getEffect()->_image) : &(getEffect()->_imageStack[imgId]);
int width = qimage->width();
int height = qimage->height();
@@ -397,14 +373,15 @@ PyObject* EffectModule::wrapImageShow(PyObject *self, PyObject *args)
}
memcpy(image.memptr(), binaryImage.data(), binaryImage.size());
effect->setInputImage(effect->_priority, image, timeout, false);
getEffect()->setInputImage(getEffect()->_priority, image, timeout, false);
return Py_BuildValue("");
}
PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
PyObject * bytearray = nullptr;
@@ -412,8 +389,8 @@ PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args)
int startRY = 0;
int startX = 0;
int startY = 0;
int endX, width = effect->_imageSize.width();
int endY, height = effect->_imageSize.height();
int endX, width = getEffect()->_imageSize.width();
int endY, height = getEffect()->_imageSize.height();
int spread = 0;
bool argsOK = false;
@@ -452,9 +429,9 @@ PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args)
}
gradient.setSpread(static_cast<QGradient::Spread>(spread));
effect->_painter->fillRect(myQRect, gradient);
getEffect()->_painter->fillRect(myQRect, gradient);
return Py_BuildValue("");
Py_RETURN_NONE;
}
else
{
@@ -473,15 +450,16 @@ PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageConicalGradient(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
PyObject * bytearray = nullptr;
int centerX, centerY, angle;
int startX = 0;
int startY = 0;
int width = effect->_imageSize.width();
int height = effect->_imageSize.height();
int width = getEffect()->_imageSize.width();
int height = getEffect()->_imageSize.height();
bool argsOK = false;
@@ -519,9 +497,9 @@ PyObject* EffectModule::wrapImageConicalGradient(PyObject *self, PyObject *args)
));
}
effect->_painter->fillRect(myQRect, gradient);
getEffect()->_painter->fillRect(myQRect, gradient);
return Py_BuildValue("");
Py_RETURN_NONE;
}
else
{
@@ -541,15 +519,16 @@ PyObject* EffectModule::wrapImageConicalGradient(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageRadialGradient(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
PyObject * bytearray = nullptr;
int centerX, centerY, radius, focalX, focalY, focalRadius, spread;
int startX = 0;
int startY = 0;
int width = effect->_imageSize.width();
int height = effect->_imageSize.height();
int width = getEffect()->_imageSize.width();
int height = getEffect()->_imageSize.height();
bool argsOK = false;
@@ -600,9 +579,9 @@ PyObject* EffectModule::wrapImageRadialGradient(PyObject *self, PyObject *args)
}
gradient.setSpread(static_cast<QGradient::Spread>(spread));
effect->_painter->fillRect(myQRect, gradient);
getEffect()->_painter->fillRect(myQRect, gradient);
return Py_BuildValue("");
Py_RETURN_NONE;
}
else
{
@@ -621,7 +600,9 @@ PyObject* EffectModule::wrapImageRadialGradient(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageDrawPolygon(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
PyObject * bytearray = nullptr;
int argCount = PyTuple_Size(args);
@@ -654,14 +635,14 @@ PyObject* EffectModule::wrapImageDrawPolygon(PyObject *self, PyObject *args)
points.append(QPoint((int)(data[idx]),(int)(data[idx+1])));
}
QPainter * painter = effect->_painter;
QPainter * painter = getEffect()->_painter;
QPen oldPen = painter->pen();
QPen newPen(QColor(r,g,b,a));
painter->setPen(newPen);
painter->setBrush(QBrush(QColor(r,g,b,a), Qt::SolidPattern));
painter->drawPolygon(points);
painter->setPen(oldPen);
return Py_BuildValue("");
Py_RETURN_NONE;
}
else
{
@@ -680,7 +661,9 @@ PyObject* EffectModule::wrapImageDrawPolygon(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
PyObject * bytearray = nullptr;
QString brush;
@@ -714,7 +697,7 @@ PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args)
if (argsOK)
{
QPainter * painter = effect->_painter;
QPainter * painter = getEffect()->_painter;
startAngle = qMax(qMin(startAngle,360),0);
spanAngle = qMax(qMin(spanAngle,360),-360);
@@ -745,7 +728,7 @@ PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args)
}
painter->setBrush(gradient);
return Py_BuildValue("");
Py_RETURN_NONE;
}
else
{
@@ -768,22 +751,23 @@ PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args)
painter->setPen(newPen);
painter->drawPie(centerX - radius, centerY - radius, centerX + radius, centerY + radius, startAngle * 16, spanAngle * 16);
painter->setPen(oldPen);
return Py_BuildValue("");
Py_RETURN_NONE;
}
return nullptr;
}
PyObject* EffectModule::wrapImageSolidFill(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int r, g, b;
int a = 255;
int startX = 0;
int startY = 0;
int width = effect->_imageSize.width();
int height = effect->_imageSize.height();
int width = getEffect()->_imageSize.width();
int height = getEffect()->_imageSize.height();
bool argsOK = false;
@@ -807,8 +791,8 @@ PyObject* EffectModule::wrapImageSolidFill(PyObject *self, PyObject *args)
if (argsOK)
{
QRect myQRect(startX,startY,width,height);
effect->_painter->fillRect(myQRect, QColor(r,g,b,a));
return Py_BuildValue("");
getEffect()->_painter->fillRect(myQRect, QColor(r,g,b,a));
Py_RETURN_NONE;
}
return nullptr;
}
@@ -816,7 +800,8 @@ PyObject* EffectModule::wrapImageSolidFill(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int r, g, b;
@@ -824,8 +809,8 @@ PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args)
int startX = 0;
int startY = 0;
int thick = 1;
int endX = effect->_imageSize.width();
int endY = effect->_imageSize.height();
int endX = getEffect()->_imageSize.width();
int endY = getEffect()->_imageSize.height();
bool argsOK = false;
@@ -840,7 +825,7 @@ PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args)
if (argsOK)
{
QPainter * painter = effect->_painter;
QPainter * painter = getEffect()->_painter;
QRect myQRect(startX, startY, endX, endY);
QPen oldPen = painter->pen();
QPen newPen(QColor(r,g,b,a));
@@ -849,14 +834,15 @@ PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args)
painter->drawLine(startX, startY, endX, endY);
painter->setPen(oldPen);
return Py_BuildValue("");
Py_RETURN_NONE;
}
return nullptr;
}
PyObject* EffectModule::wrapImageDrawPoint(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int r, g, b, x, y;
@@ -876,7 +862,7 @@ PyObject* EffectModule::wrapImageDrawPoint(PyObject *self, PyObject *args)
if (argsOK)
{
QPainter * painter = effect->_painter;
QPainter * painter = getEffect()->_painter;
QPen oldPen = painter->pen();
QPen newPen(QColor(r,g,b,a));
newPen.setWidth(thick);
@@ -884,14 +870,15 @@ PyObject* EffectModule::wrapImageDrawPoint(PyObject *self, PyObject *args)
painter->drawPoint(x, y);
painter->setPen(oldPen);
return Py_BuildValue("");
Py_RETURN_NONE;
}
return nullptr;
}
PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int r, g, b;
@@ -899,8 +886,8 @@ PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
int startX = 0;
int startY = 0;
int thick = 1;
int width = effect->_imageSize.width();
int height = effect->_imageSize.height();
int width = getEffect()->_imageSize.width();
int height = getEffect()->_imageSize.height();
bool argsOK = false;
@@ -915,7 +902,7 @@ PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
if (argsOK)
{
QPainter * painter = effect->_painter;
QPainter * painter = getEffect()->_painter;
QRect myQRect(startX,startY,width,height);
QPen oldPen = painter->pen();
QPen newPen(QColor(r,g,b,a));
@@ -924,7 +911,7 @@ PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
painter->drawRect(startX, startY, width, height);
painter->setPen(oldPen);
return Py_BuildValue("");
Py_RETURN_NONE;
}
return nullptr;
}
@@ -932,15 +919,16 @@ PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageSetPixel(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int r, g, b, x, y;
if ( argCount == 5 && PyArg_ParseTuple(args, "iiiii", &x, &y, &r, &g, &b ) )
{
effect->_image.setPixel(x,y,qRgb(r,g,b));
return Py_BuildValue("");
getEffect()->_image.setPixel(x,y,qRgb(r,g,b));
Py_RETURN_NONE;
}
return nullptr;
@@ -949,14 +937,15 @@ PyObject* EffectModule::wrapImageSetPixel(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageGetPixel(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int x, y;
if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &x, &y) )
{
QRgb rgb = effect->_image.pixel(x,y);
QRgb rgb = getEffect()->_image.pixel(x,y);
return Py_BuildValue("iii",qRed(rgb),qGreen(rgb),qBlue(rgb));
}
return nullptr;
@@ -964,52 +953,60 @@ PyObject* EffectModule::wrapImageGetPixel(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageSave(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
QImage img(effect->_image.copy());
effect->_imageStack.append(img);
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
return Py_BuildValue("i", effect->_imageStack.size()-1);
QImage img(getEffect()->_image.copy());
getEffect()->_imageStack.append(img);
return Py_BuildValue("i", getEffect()->_imageStack.size()-1);
}
PyObject* EffectModule::wrapImageMinSize(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int w, h;
int width = effect->_imageSize.width();
int height = effect->_imageSize.height();
int width = getEffect()->_imageSize.width();
int height = getEffect()->_imageSize.height();
if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &w, &h) )
{
if (width<w || height<h)
{
delete effect->_painter;
delete getEffect()->_painter;
effect->_image = effect->_image.scaled(qMax(width,w),qMax(height,h), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
effect->_imageSize = effect->_image.size();
effect->_painter = new QPainter(&(effect->_image));
getEffect()->_image = getEffect()->_image.scaled(qMax(width,w),qMax(height,h), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
getEffect()->_imageSize = getEffect()->_image.size();
getEffect()->_painter = new QPainter(&(getEffect()->_image));
}
return Py_BuildValue("ii", effect->_image.width(), effect->_image.height());
return Py_BuildValue("ii", getEffect()->_image.width(), getEffect()->_image.height());
}
return nullptr;
}
PyObject* EffectModule::wrapImageWidth(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
return Py_BuildValue("i", effect->_imageSize.width());
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
return Py_BuildValue("i", getEffect()->_imageSize.width());
}
PyObject* EffectModule::wrapImageHeight(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
return Py_BuildValue("i", effect->_imageSize.height());
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
return Py_BuildValue("i", getEffect()->_imageSize.height());
}
PyObject* EffectModule::wrapImageCRotate(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int angle;
@@ -1017,15 +1014,16 @@ PyObject* EffectModule::wrapImageCRotate(PyObject *self, PyObject *args)
if ( argCount == 1 && PyArg_ParseTuple(args, "i", &angle ) )
{
angle = qMax(qMin(angle,360),0);
effect->_painter->rotate(angle);
return Py_BuildValue("");
getEffect()->_painter->rotate(angle);
Py_RETURN_NONE;
}
return nullptr;
}
PyObject* EffectModule::wrapImageCOffset(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int offsetX = 0;
int offsetY = 0;
@@ -1036,60 +1034,31 @@ PyObject* EffectModule::wrapImageCOffset(PyObject *self, PyObject *args)
PyArg_ParseTuple(args, "ii", &offsetX, &offsetY );
}
effect->_painter->translate(QPoint(offsetX,offsetY));
return Py_BuildValue("");
getEffect()->_painter->translate(QPoint(offsetX,offsetY));
Py_RETURN_NONE;
}
PyObject* EffectModule::wrapImageCShear(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int sh,sv;
int argCount = PyTuple_Size(args);
if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &sh, &sv ))
{
effect->_painter->shear(sh,sv);
return Py_BuildValue("");
getEffect()->_painter->shear(sh,sv);
Py_RETURN_NONE;
}
return nullptr;
}
PyObject* EffectModule::wrapImageResetT(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
effect->_painter->resetTransform();
return Py_BuildValue("");
}
Effect * EffectModule::getEffect()
{
// extract the module from the runtime
PyObject * module = PyObject_GetAttrString(PyImport_AddModule("__main__"), "hyperion");
if (!PyModule_Check(module))
{
// something is wrong
Py_XDECREF(module);
Error(Logger::getInstance("EFFECTENGINE"), "Unable to retrieve the effect object from the Python runtime");
return nullptr;
}
// retrieve the capsule with the effect
PyObject * effectCapsule = PyObject_GetAttrString(module, "__effectObj");
Py_XDECREF(module);
if (!PyCapsule_CheckExact(effectCapsule))
{
// something is wrong
Py_XDECREF(effectCapsule);
Error(Logger::getInstance("EFFECTENGINE"), "Unable to retrieve the effect object from the Python runtime");
return nullptr;
}
// Get the effect from the capsule
Effect * effect = reinterpret_cast<Effect *>(PyCapsule_GetPointer(effectCapsule, nullptr));
Py_XDECREF(effectCapsule);
return effect;
getEffect()->_painter->resetTransform();
Py_RETURN_NONE;
}

View File

@@ -6,7 +6,6 @@
#include <QTimer>
#include <QRgb>
#include <hyperion/Hyperion.h>
FlatBufferClient::FlatBufferClient(QTcpSocket* socket, const int &timeout, QObject *parent)
: QObject(parent)
, _log(Logger::getInstance("FLATBUFSERVER"))
@@ -15,7 +14,6 @@ FlatBufferClient::FlatBufferClient(QTcpSocket* socket, const int &timeout, QObje
, _timeoutTimer(new QTimer(this))
, _timeout(timeout * 1000)
, _priority()
, _hyperion(Hyperion::getInstance())
{
// timer setup
_timeoutTimer->setSingleShot(true);
@@ -70,8 +68,10 @@ void FlatBufferClient::forceClose()
void FlatBufferClient::disconnected()
{
Debug(_log, "Socket Closed");
_socket->deleteLater();
_hyperion->clear(_priority);
_socket->deleteLater();
if (_priority != 0 && _priority >= 100 && _priority < 200)
emit clearGlobalInput(_priority);
emit clientDisconnected();
}
@@ -101,16 +101,35 @@ void FlatBufferClient::handleColorCommand(const hyperionnet::Color *colorReq)
color.blue = qBlue(rgbData);
// set output
_hyperion->setColor(_priority, color, colorReq->duration());
emit setGlobalInputColor(_priority, color, colorReq->duration());
// send reply
sendSuccessReply();
}
void FlatBufferClient::registationRequired(const int priority)
{
if (_priority == priority)
{
auto reply = hyperionnet::CreateReplyDirect(_builder, nullptr, -1, -1);
_builder.Finish(reply);
// send reply
sendMessage();
}
}
void FlatBufferClient::handleRegisterCommand(const hyperionnet::Register *regReq)
{
if (regReq->priority() < 100 || regReq->priority() >= 200)
{
// Warning(_log, "Register request from client %s contains invalid priority %d. Valid rage is between 100 and 199.", QSTRING_CSTR(_clientAddress), regReq->priority());
sendErrorReply("The priority " + std::to_string(regReq->priority()) + " is not in the priority range between 100 and 199.");
return;
}
_priority = regReq->priority();
_hyperion->registerInput(_priority, hyperion::COMP_FLATBUFSERVER, regReq->origin()->c_str()+_clientAddress);
emit registerGlobalInput(_priority, hyperion::COMP_FLATBUFSERVER, regReq->origin()->c_str()+_clientAddress);
auto reply = hyperionnet::CreateReplyDirect(_builder, nullptr, -1, (_priority ? _priority : -1));
_builder.Finish(reply);
@@ -140,7 +159,7 @@ void FlatBufferClient::handleImageCommand(const hyperionnet::Image *image)
Image<ColorRgb> imageDest(width, height);
memmove(imageDest.memptr(), imageData->data(), imageData->size());
_hyperion->setInputImage(_priority, imageDest, duration);
emit setGlobalInputImage(_priority, imageDest, duration);
}
// send reply
@@ -154,7 +173,7 @@ void FlatBufferClient::handleClearCommand(const hyperionnet::Clear *clear)
const int priority = clear->priority();
if (priority == -1) {
_hyperion->clearall();
emit clearAllGlobalInput();
}
else {
// Check if we are clearing ourselves.
@@ -162,7 +181,7 @@ void FlatBufferClient::handleClearCommand(const hyperionnet::Clear *clear)
_priority = -1;
}
_hyperion->clear(priority);
emit clearGlobalInput(priority);
}
sendSuccessReply();

View File

@@ -12,10 +12,9 @@
class QTcpSocket;
class QTimer;
class Hyperion;
namespace flatbuf {
class HyperionRequest;
class HyperionRequest;
}
///
@@ -37,12 +36,27 @@ signals:
///
/// @brief forward register data to HyperionDaemon
///
void registerGlobalInput(const int priority, const hyperion::Components& component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = 0);
void registerGlobalInput(const int priority, const hyperion::Components& component, const QString& origin = "FlatBuffer", const QString& owner = "", unsigned smooth_cfg = 0);
///
/// @brief Forward clear command to HyperionDaemon
///
void clearGlobalInput(const int priority);
///
/// @brief Forward clearAll command to HyperionDaemon
///
void clearAllGlobalInput(bool forceClearAll=false);
///
/// @brief forward prepared image to HyperionDaemon
///
const bool setGlobalInputImage(const int priority, const Image<ColorRgb>& image, const int timeout_ms = -1);
const bool setGlobalInputImage(const int priority, const Image<ColorRgb>& image, const int timeout_ms, const bool& clearEffect = false);
///
/// @brief Forward requested color
///
void setGlobalInputColor(const int priority, const ColorRgb &ledColor, const int timeout_ms, const QString& origin = "FlatBuffer" ,bool clearEffects = true);
///
/// @brief Emits whenever the client disconnected
@@ -50,6 +64,11 @@ signals:
void clientDisconnected();
public slots:
///
/// @brief Requests a registration from the client
///
void registationRequired(const int priority);
///
/// @brief close the socket and call disconnected()
///
@@ -125,7 +144,6 @@ private:
QTimer *_timeoutTimer;
int _timeout;
int _priority;
Hyperion* _hyperion;
QByteArray _receiveBuffer;

View File

@@ -4,7 +4,7 @@
// Qt includes
#include <QRgb>
// protoserver includes
// flatbuffer includes
#include <flatbufserver/FlatBufferConnection.h>
FlatBufferConnection::FlatBufferConnection(const QString& origin, const QString & address, const int& priority, const bool& skipReply)
@@ -210,12 +210,15 @@ bool FlatBufferConnection::parseReply(const hyperionnet::Reply *reply)
}
// We got a registered reply.
if (registered != -1 && registered != _priority)
if (registered == -1 || registered != _priority)
_registered = false;
else
_registered = true;
return true;
}
else
throw std::runtime_error(reply->error()->str());
return false;
}

View File

@@ -1,6 +1,10 @@
#include <flatbufserver/FlatBufferServer.h>
#include "FlatBufferClient.h"
// util
#include <utils/NetOrigin.h>
#include <utils/GlobalSignals.h>
// qt
#include <QJsonObject>
#include <QTcpServer>
@@ -24,6 +28,7 @@ FlatBufferServer::~FlatBufferServer()
void FlatBufferServer::initServer()
{
_netOrigin = NetOrigin::getInstance();
connect(_server, &QTcpServer::newConnection, this, &FlatBufferServer::newConnection);
// apply config
@@ -58,11 +63,22 @@ void FlatBufferServer::newConnection()
{
if(QTcpSocket* socket = _server->nextPendingConnection())
{
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this);
// internal
connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
_openConnections.append(client);
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
{
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this);
// internal
connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
connect(client, &FlatBufferClient::registerGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::registerGlobalInput);
connect(client, &FlatBufferClient::clearGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::clearGlobalInput);
connect(client, &FlatBufferClient::clearAllGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::clearAllGlobalInput);
connect(client, &FlatBufferClient::setGlobalInputImage, GlobalSignals::getInstance(), &GlobalSignals::setGlobalImage);
connect(client, &FlatBufferClient::setGlobalInputColor, GlobalSignals::getInstance(), &GlobalSignals::setGlobalColor);
connect(GlobalSignals::getInstance(), &GlobalSignals::globalRegRequired, client, &FlatBufferClient::registationRequired);
_openConnections.append(client);
}
else
socket->close();
}
}
}

View File

@@ -96,6 +96,7 @@ void QtGrabber::geometryChanged(const QRect &geo)
int QtGrabber::grabFrame(Image<ColorRgb> & image)
{
if (!_enabled) return 0;
if(_screen == nullptr)
{
// reinit, this will disable capture on failure

View File

@@ -17,6 +17,7 @@
#include <linux/videodev2.h>
#include <hyperion/Hyperion.h>
#include <hyperion/HyperionIManager.h>
#include <QDirIterator>
#include <QFileInfo>
@@ -57,9 +58,9 @@ V4L2Grabber::V4L2Grabber(const QString & device
setPixelDecimation(pixelDecimation);
getV4Ldevices();
// listen for component change for build-in grabber only
if (Hyperion::_hyperion)
connect(Hyperion::getInstance(), &Hyperion::componentStateChanged, this, &V4L2Grabber::componentStateChanged);
// connect componentStateChange only for build-in grabber
if (HyperionIManager::HIMinstance)
connect(this, &Grabber::componentStateChanged, this, &V4L2Grabber::componentStateChanged);
// init
setDeviceVideoStandard(device, videoStandard);

View File

@@ -21,8 +21,10 @@ V4L2Wrapper::V4L2Wrapper(const QString &device,
qRegisterMetaType<Image<ColorRgb>>("Image<ColorRgb>");
// Handle the image in the captured thread using a direct connection
QObject::connect(&_grabber, SIGNAL(newFrame(Image<ColorRgb>)), this, SLOT(newFrame(Image<ColorRgb>)), Qt::DirectConnection);
QObject::connect(&_grabber, SIGNAL(readError(const char*)), this, SLOT(readError(const char*)), Qt::DirectConnection);
connect(&_grabber, SIGNAL(newFrame(Image<ColorRgb>)), this, SLOT(newFrame(Image<ColorRgb>)), Qt::DirectConnection);
connect(&_grabber, SIGNAL(readError(const char*)), this, SLOT(readError(const char*)), Qt::DirectConnection);
connect(this, &V4L2Wrapper::componentStateChanged, _ggrabber, &Grabber::componentStateChanged);
}
bool V4L2Wrapper::start()

View File

@@ -0,0 +1,169 @@
#include <hyperion/AuthManager.h>
// db
#include <db/AuthTable.h>
#include <db/MetaTable.h>
// qt
#include <QJsonObject>
#include <QTimer>
AuthManager* AuthManager::manager = nullptr;
AuthManager::AuthManager(QObject* parent)
: QObject(parent)
, _authTable(new AuthTable(this))
, _metaTable(new MetaTable(this))
, _pendingRequests()
, _authRequired(true)
, _timer(new QTimer(this))
{
AuthManager::manager = this;
// get uuid
_uuid = _metaTable->getUUID();
// setup timer
_timer->setInterval(1000);
connect(_timer, &QTimer::timeout, this, &AuthManager::checkTimeout);
// init with default user and password
if(!_authTable->userExist("Hyperion"))
{
_authTable->createUser("Hyperion","hyperion");
}
}
bool & AuthManager::isAuthRequired()
{
return _authRequired;
}
bool & AuthManager::isLocalAuthRequired()
{
return _localAuthRequired;
}
const AuthManager::AuthDefinition AuthManager::createToken(const QString& comment)
{
const QString token = QUuid::createUuid().toString().mid(1, 36);
const QString id = QUuid::createUuid().toString().mid(1, 36).left(5);
_authTable->createToken(token, comment, id);
AuthDefinition def;
def.comment = comment;
def.token = token;
def.id = id;
return def;
}
const QVector<AuthManager::AuthDefinition> AuthManager::getTokenList()
{
QVector<QVariantMap> vector = _authTable->getTokenList();
QVector<AuthManager::AuthDefinition> finalVec;
for(const auto& entry : vector)
{
AuthDefinition def;
def.comment = entry["comment"].toString();
def.id = entry["id"].toString();
def.lastUse = entry["last_use"].toString();
// don't add empty ids
if(!entry["id"].toString().isEmpty())
finalVec.append(def);
}
return finalVec;
}
bool AuthManager::isUserAuthorized(const QString& user, const QString& pw)
{
return _authTable->isUserAuthorized(user, pw);
}
bool AuthManager::isTokenAuthorized(const QString& token)
{
return _authTable->tokenExist(token);
}
void AuthManager::setNewTokenRequest(QObject* caller, const QString& comment, const QString& id)
{
if(!_pendingRequests.contains(id))
{
AuthDefinition newDef {id, comment, caller, uint64_t(QDateTime::currentMSecsSinceEpoch()+60000)};
_pendingRequests[id] = newDef;
_timer->start();
emit newPendingTokenRequest(id, comment);
}
}
bool AuthManager::acceptTokenRequest(const QString& id)
{
if(_pendingRequests.contains(id))
{
const QString token = QUuid::createUuid().toString().remove("{").remove("}");
AuthDefinition def = _pendingRequests.take(id);
_authTable->createToken(token, def.comment, id);
emit tokenResponse(true, def.caller, token, def.comment, id);
return true;
}
return false;
}
bool AuthManager::denyTokenRequest(const QString& id)
{
if(_pendingRequests.contains(id))
{
AuthDefinition def = _pendingRequests.take(id);
emit tokenResponse(false, def.caller, QString(), def.comment, id);
return true;
}
return false;
}
const QMap<QString, AuthManager::AuthDefinition> AuthManager::getPendingRequests()
{
return _pendingRequests;
}
bool AuthManager::deleteToken(const QString& id)
{
if(_authTable->deleteToken(id))
{
//emit tokenDeleted(token);
return true;
}
return false;
}
void AuthManager::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
{
if(type == settings::NETWORK)
{
const QJsonObject& obj = config.object();
_authRequired = obj["apiAuth"].toBool(true);
_localAuthRequired = obj["localApiAuth"].toBool(false);
}
}
void AuthManager::checkTimeout()
{
const uint64_t now = QDateTime::currentMSecsSinceEpoch();
QMapIterator<QString, AuthDefinition> i(_pendingRequests);
while (i.hasNext())
{
i.next();
const AuthDefinition& def = i.value();
if(def.timeoutTime <= now)
{
emit tokenResponse(false, def.caller, QString(), def.comment, def.id);
_pendingRequests.remove(i.key());
}
}
// abort if empty
if(_pendingRequests.isEmpty())
_timer->stop();
}

View File

@@ -25,5 +25,6 @@ target_link_libraries(hyperion
bonjour
boblightserver
effectengine
database
${QT_LIBRARIES}
)

View File

@@ -81,6 +81,8 @@ void CaptureCont::setSystemCaptureEnable(const bool& enable)
{
disconnect(GlobalSignals::getInstance(), &GlobalSignals::setSystemImage, 0, 0);
_hyperion->clear(_systemCaptPrio);
_systemInactiveTimer->stop();
_systemCaptName = "";
}
_systemCaptEnabled = enable;
_hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_GRABBER, enable);
@@ -103,6 +105,7 @@ void CaptureCont::setV4LCaptureEnable(const bool& enable)
disconnect(GlobalSignals::getInstance(), &GlobalSignals::setV4lImage, 0, 0);
_hyperion->clear(_v4lCaptPrio);
_v4lInactiveTimer->stop();
_v4lCaptName = "";
}
_v4lCaptEnabled = enable;
_hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_V4L, enable);

View File

@@ -11,7 +11,7 @@ ComponentRegister::ComponentRegister(Hyperion* hyperion)
{
// init all comps to false
QVector<hyperion::Components> vect;
vect << COMP_ALL << COMP_SMOOTHING << COMP_BLACKBORDER << COMP_FORWARDER << COMP_UDPLISTENER << COMP_BOBLIGHTSERVER << COMP_GRABBER << COMP_V4L << COMP_LEDDEVICE;
vect << COMP_ALL << COMP_SMOOTHING << COMP_BLACKBORDER << COMP_FORWARDER << COMP_BOBLIGHTSERVER << COMP_GRABBER << COMP_V4L << COMP_LEDDEVICE;
for(auto e : vect)
{
_componentStates.emplace(e, ((e == COMP_ALL) ? true : false));

View File

@@ -25,7 +25,7 @@ GrabberWrapper::GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned
connect(_timer, &QTimer::timeout, this, &GrabberWrapper::action);
// connect the image forwarding
_grabberName.startsWith("V4L")
(_grabberName.startsWith("V4L"))
? connect(this, &GrabberWrapper::systemImage, GlobalSignals::getInstance(), &GlobalSignals::setV4lImage)
: connect(this, &GrabberWrapper::systemImage, GlobalSignals::getInstance(), &GlobalSignals::setSystemImage);
}

View File

@@ -5,17 +5,9 @@
#include <unistd.h>
// QT includes
#include <QDateTime>
#include <QThread>
#include <QRegExp>
#include <QString>
#include <QStringList>
#include <QCryptographicHash>
#include <QTimer>
#include <QFile>
#include <QFileInfo>
#include <QHostInfo>
#include <QCryptographicHash>
#include <QThread>
// hyperion include
#include <hyperion/Hyperion.h>
@@ -25,6 +17,7 @@
// utils
#include <utils/hyperion.h>
#include <utils/GlobalSignals.h>
// Leddevice includes
#include <leddevice/LedDeviceWrapper.h>
@@ -35,9 +28,6 @@
// effect engine includes
#include <effectengine/EffectEngine.h>
// Hyperion Daemon
#include <../src/hyperiond/hyperiond.h>
// settingsManagaer
#include <hyperion/SettingsManager.h>
@@ -50,28 +40,10 @@
// Boblight
#include <boblightserver/BoblightServer.h>
Hyperion* Hyperion::_hyperion = nullptr;
Hyperion* Hyperion::initInstance( HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath)
{
if ( Hyperion::_hyperion != nullptr )
throw std::runtime_error("Hyperion::initInstance can be called only one time");
Hyperion::_hyperion = new Hyperion(daemon, instance, configFile, rootPath);
return Hyperion::_hyperion;
}
Hyperion* Hyperion::getInstance()
{
if ( Hyperion::_hyperion == nullptr )
throw std::runtime_error("Hyperion::getInstance used without call of Hyperion::initInstance before");
return Hyperion::_hyperion;
}
Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath)
: _daemon(daemon)
, _settingsManager(new SettingsManager(this, instance, configFile))
Hyperion::Hyperion(const quint8& instance)
: QObject()
, _instIndex(instance)
, _settingsManager(new SettingsManager(instance, this))
, _componentRegister(this)
, _ledString(hyperion::createLedString(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object())))
, _ledStringClone(hyperion::createLedStringClone(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object())))
@@ -79,18 +51,33 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
, _muxer(_ledString.leds().size())
, _raw2ledAdjustment(hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object()))
, _effectEngine(nullptr)
, _messageForwarder(new MessageForwarder(this))
, _configFile(configFile)
, _rootPath(rootPath)
, _messageForwarder(nullptr)
, _log(Logger::getInstance("HYPERION"))
, _hwLedCount()
, _configHash()
, _ledGridSize(hyperion::getLedLayoutGridSize(getSetting(settings::LEDS).array()))
, _prevCompId(hyperion::COMP_INVALID)
, _ledBuffer(_ledString.leds().size(), ColorRgb::BLACK)
{
}
Hyperion::~Hyperion()
{
freeObjects(false);
}
void Hyperion::start()
{
// forward settings changed to Hyperion
connect(_settingsManager, &SettingsManager::settingsChanged, this, &Hyperion::settingsChanged);
// get newVideoMode from HyperionIManager
connect(this, &Hyperion::newVideoMode, this, &Hyperion::handleNewVideoMode);
if (!_raw2ledAdjustment->verifyAdjustments())
{
Warning(_log, "At least one led has no color calibration, please add all leds from your led layout to an 'LED index' field!");
}
// handle hwLedCount
_hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount());
@@ -100,12 +87,14 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
{
_ledStringColorOrder.push_back(led.colorOrder);
}
for (Led& led : _ledStringClone.leds())
{
_ledStringColorOrder.insert(_ledStringColorOrder.begin() + led.index, led.colorOrder);
}
// connect Hyperion::update with Muxer visible priority changes as muxer updates independent
connect(&_muxer, &PriorityMuxer::visiblePriorityChanged, this, &Hyperion::update);
// listens for ComponentRegister changes of COMP_ALL to perform core enable/disable actions
connect(&_componentRegister, &ComponentRegister::updatedComponentState, this, &Hyperion::updatedComponentState);
@@ -128,22 +117,14 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
_deviceSmooth = new LinearColorSmoothing(getSetting(settings::SMOOTHING), this);
connect(this, &Hyperion::settingsChanged, _deviceSmooth, &LinearColorSmoothing::handleSettingsUpdate);
// create the message forwarder only on main instance
if (_instIndex == 0)
_messageForwarder = new MessageForwarder(this);
// create the effect engine; needs to be initialized after smoothing!
_effectEngine = new EffectEngine(this);
connect(_effectEngine, &EffectEngine::effectListUpdated, this, &Hyperion::effectListUpdated);
// setup config state checks and initial shot
checkConfigState();
if(_fsWatcher.addPath(_configFile))
QObject::connect(&_fsWatcher, &QFileSystemWatcher::fileChanged, this, &Hyperion::checkConfigState);
else
{
_cTimer = new QTimer(this);
Warning(_log,"Filesystem Observer failed for file: %s, use fallback timer", _configFile.toStdString().c_str());
connect(_cTimer, SIGNAL(timeout()), this, SLOT(checkConfigState()));
_cTimer->start(2000);
}
// initial startup effect
hyperion::handleInitialEffect(this, getSetting(settings::FGEFFECT).object());
@@ -153,6 +134,13 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
// create the Daemon capture interface
_captureCont = new CaptureCont(this);
// forwards global signals to the corresponding slots
connect(GlobalSignals::getInstance(), &GlobalSignals::registerGlobalInput, this, &Hyperion::registerInput);
connect(GlobalSignals::getInstance(), &GlobalSignals::clearGlobalInput, this, &Hyperion::clear);
connect(GlobalSignals::getInstance(), &GlobalSignals::clearAllGlobalInput, this, &Hyperion::clearall);
connect(GlobalSignals::getInstance(), &GlobalSignals::setGlobalColor, this, &Hyperion::setColor);
connect(GlobalSignals::getInstance(), &GlobalSignals::setGlobalImage, this, &Hyperion::setInputImage);
// if there is no startup / background eff and no sending capture interface we probably want to push once BLACK (as PrioMuxer won't emit a prioritiy change)
update();
@@ -160,13 +148,15 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
_boblightServer = new BoblightServer(this, getSetting(settings::BOBLSERVER));
connect(this, &Hyperion::settingsChanged, _boblightServer, &BoblightServer::handleSettingsUpdate);
// set unique id
_id = QString(QCryptographicHash::hash(getConfigFileName().toLocal8Bit(),QCryptographicHash::Sha1).toHex());
// instance inited
emit started();
// enter thread event loop
}
Hyperion::~Hyperion()
void Hyperion::stop()
{
freeObjects(false);
emit finished();
thread()->wait();
}
void Hyperion::freeObjects(bool emitCloseSignal)
@@ -209,9 +199,6 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
const QJsonArray leds = config.array();
// // lock update()
// _lockUpdate = true;
// stop and cache all running effects, as effects depend heavily on ledlayout
_effectEngine->cacheRunningEffects();
@@ -238,23 +225,16 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
// handle hwLedCount update
_hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount());
// update led count in device
//_ledDeviceWrapper->setLedCount(_hwLedCount);
// change in leds are also reflected in adjustment
delete _raw2ledAdjustment;
_raw2ledAdjustment = hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object());
// start cached effects
_effectEngine->startCachedEffects();
// // unlock
// _lockUpdate = false;
}
else if(type == settings::DEVICE)
{
QMutexLocker lock(&_changes);
// _lockUpdate = true;
QJsonObject dev = config.object();
// handle hwLedCount update
@@ -268,18 +248,11 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
_imageProcessor->setLedString(_ledString);
}
/* // reinit led device type on change
if(_device->getActiveDevice() != dev["type"].toString("file").toLower())
{
}
// update led count
_device->setLedCount(_hwLedCount);
*/
// do always reinit until the led devices can handle dynamic changes
dev["currentLedCount"] = int(_hwLedCount); // Inject led count info
_ledDeviceWrapper->createLedDevice(dev);
// _lockUpdate = false;
}
// update once to push single color sets / adjustments/ ledlayout resizes and update ledBuffer color
update();
}
@@ -294,12 +267,6 @@ bool Hyperion::saveSettings(QJsonObject config, const bool& correct)
return _settingsManager->saveSettings(config, correct);
}
QString Hyperion::getConfigFileName() const
{
QFileInfo cF(_configFile);
return cF.fileName();
}
int Hyperion::getLatchTime() const
{
return _ledDeviceWrapper->getLatchTime();
@@ -315,40 +282,6 @@ unsigned Hyperion::getLedCount() const
return _ledString.leds().size();
}
void Hyperion::checkConfigState(QString cfile)
{
// Check config modifications
QFile f(_configFile);
if (f.open(QFile::ReadOnly))
{
QCryptographicHash hash(QCryptographicHash::Sha1);
if (hash.addData(&f))
{
if (_configHash.size() == 0)
{
_configHash = hash.result();
}
_configMod = _configHash != hash.result() ? true : false;
}
}
f.close();
if(_prevConfigMod != _configMod)
{
_prevConfigMod = _configMod;
}
// Check config writeable
QFile file(_configFile);
QFileInfo fileInfo(file);
_configWrite = fileInfo.isWritable() && fileInfo.isReadable() ? true : false;
if(_prevConfigWrite != _configWrite)
{
_prevConfigWrite = _configWrite;
}
}
void Hyperion::setSourceAutoSelectEnabled(bool enabled)
{
if(_muxer.setSourceAutoSelectEnabled(enabled))
@@ -372,6 +305,7 @@ void Hyperion::setNewComponentState(const hyperion::Components& component, const
void Hyperion::setComponentState(const hyperion::Components component, const bool state)
{
// TODO REMOVE THIS STEP
emit componentStateChanged(component, state);
}
@@ -399,6 +333,12 @@ bool Hyperion::setInput(const int priority, const std::vector<ColorRgb>& ledColo
bool Hyperion::setInputImage(const int priority, const Image<ColorRgb>& image, int64_t timeout_ms, const bool& clearEffect)
{
if (!_muxer.hasPriority(priority))
{
emit GlobalSignals::getInstance()->globalRegRequired(priority);
return false;
}
if(_muxer.setInputImage(priority, image, timeout_ms))
{
// clear effect if this call does not come from an effect
@@ -419,7 +359,7 @@ bool Hyperion::setInputInactive(const quint8& priority)
return _muxer.setInputInactive(priority);
}
void Hyperion::setColor(int priority, const ColorRgb &color, const int timeout_ms, const QString& origin, bool clearEffects)
void Hyperion::setColor(const int priority, const ColorRgb &color, const int timeout_ms, const QString& origin, bool clearEffects)
{
// clear effect if this call does not come from an effect
if(clearEffects)
@@ -431,8 +371,10 @@ void Hyperion::setColor(int priority, const ColorRgb &color, const int timeout_m
// register color
registerInput(priority, hyperion::COMP_COLOR, origin);
// write color to muxer
// write color to muxer & queuePush
setInput(priority, ledColors, timeout_ms);
if(timeout_ms <= 0)
_muxer.queuePush();
}
const QStringList & Hyperion::getAdjustmentIds() const
@@ -451,7 +393,7 @@ void Hyperion::adjustmentsUpdated()
update();
}
bool Hyperion::clear(int priority)
bool Hyperion::clear(const int priority)
{
// send clear signal to the effect engine
// (outside the check so the effect gets cleared even when the effect is not sending colors)
@@ -552,7 +494,7 @@ void Hyperion::setVideoMode(const VideoMode& mode)
const VideoMode & Hyperion::getCurrentVideoMode()
{
return _daemon->getVideoMode();
return _currVideoMode;
}
const QString & Hyperion::getActiveDevice()
@@ -572,9 +514,6 @@ void Hyperion::updatedComponentState(const hyperion::Components comp, const bool
_prevCompId = comp;
_raw2ledAdjustment->setBacklightEnabled((_prevCompId != hyperion::COMP_COLOR && _prevCompId != hyperion::COMP_EFFECT));
}
if(comp == hyperion::COMP_ALL)
_muxer.setEnable(state); // first muxer to update all inputs
}
void Hyperion::update()

View File

@@ -0,0 +1,203 @@
#include <hyperion/HyperionIManager.h>
// hyperion
#include <hyperion/Hyperion.h>
#include <db/InstanceTable.h>
// qt
#include <QThread>
HyperionIManager* HyperionIManager::HIMinstance;
HyperionIManager::HyperionIManager(const QString& rootPath, QObject* parent)
: QObject(parent)
, _log(Logger::getInstance("HYPERION"))
, _instanceTable( new InstanceTable(rootPath, this) )
, _rootPath( rootPath )
{
HIMinstance = this;
qRegisterMetaType<instanceState>("instanceState");
}
Hyperion* HyperionIManager::getHyperionInstance(const quint8& instance)
{
if(_runningInstances.contains(instance))
return _runningInstances.value(instance);
Warning(_log,"The requested instance index '%d' with name '%s' isn't running, return main instance", instance, QSTRING_CSTR(_instanceTable->getNamebyIndex(instance)));
return _runningInstances.value(0);
}
const QVector<QVariantMap> HyperionIManager::getInstanceData()
{
QVector<QVariantMap> instances = _instanceTable->getAllInstances();
for( auto & entry : instances)
{
// add running state
entry["running"] = _runningInstances.contains(entry["instance"].toInt());
}
return instances;
}
void HyperionIManager::startAll()
{
for(const auto entry : _instanceTable->getAllInstances(true))
{
startInstance(entry["instance"].toInt());
}
}
void HyperionIManager::stopAll()
{
// copy the instances due to loop corruption, even with .erase() return next iter
QMap<quint8, Hyperion*> instCopy = _runningInstances;
for(const auto instance : instCopy)
{
instance->stop();
}
}
bool HyperionIManager::startInstance(const quint8& inst, const bool& block)
{
if(_instanceTable->instanceExist(inst))
{
if(!_runningInstances.contains(inst) && !_startQueue.contains(inst))
{
QThread* hyperionThread = new QThread();
Hyperion* hyperion = new Hyperion(inst);
hyperion->moveToThread(hyperionThread);
// setup thread management
connect(hyperionThread, &QThread::started, hyperion, &Hyperion::start);
connect(hyperion, &Hyperion::started, this, &HyperionIManager::handleStarted);
connect(hyperion, &Hyperion::finished, this, &HyperionIManager::handleFinished);
connect(hyperion, &Hyperion::finished, hyperionThread, &QThread::quit, Qt::DirectConnection);
// setup further connections
// from Hyperion
connect(hyperion, &Hyperion::settingsChanged, this, &HyperionIManager::settingsChanged);
connect(hyperion, &Hyperion::videoMode, this, &HyperionIManager::requestVideoMode);
connect(hyperion, &Hyperion::componentStateChanged, this, &HyperionIManager::componentStateChanged);
// to Hyperion
connect(this, &HyperionIManager::newVideoMode, hyperion, &Hyperion::newVideoMode);
// add to queue and start
_startQueue << inst;
hyperionThread->start();
// update db
_instanceTable->setLastUse(inst);
_instanceTable->setEnable(inst, true);
if(block)
{
while(!hyperionThread->isRunning()){};
}
return true;
}
Debug(_log,"Can't start Hyperion instance index '%d' with name '%s' it's already running or queued for start", inst, QSTRING_CSTR(_instanceTable->getNamebyIndex(inst)));
return false;
}
Debug(_log,"Can't start Hyperion instance index '%d' it doesn't exist in DB", inst);
return false;
}
bool HyperionIManager::stopInstance(const quint8& inst)
{
// inst 0 can't be stopped
if(!isInstAllowed(inst))
return false;
if(_instanceTable->instanceExist(inst))
{
if(_runningInstances.contains(inst))
{
// notify a ON_STOP rather sooner than later, queued signal listener should have some time to drop the pointer before it's deleted
emit instanceStateChanged(H_ON_STOP, inst);
Hyperion* hyperion = _runningInstances.value(inst);
hyperion->stop();
// update db
_instanceTable->setEnable(inst, false);
return true;
}
Debug(_log,"Can't stop Hyperion instance index '%d' with name '%s' it's not running'", inst, QSTRING_CSTR(_instanceTable->getNamebyIndex(inst)));
return false;
}
Debug(_log,"Can't stop Hyperion instance index '%d' it doesn't exist in DB", inst);
return false;
}
bool HyperionIManager::createInstance(const QString& name, const bool& start)
{
quint8 inst;
if(_instanceTable->createInstance(name, inst))
{
Info(_log,"New Hyperion instance created with name '%s'",QSTRING_CSTR(name));
emit instanceStateChanged(H_CREATED, inst, name);
emit change();
if(start)
startInstance(inst);
return true;
}
return false;
}
bool HyperionIManager::deleteInstance(const quint8& inst)
{
// inst 0 can't be deleted
if(!isInstAllowed(inst))
return false;
// stop it if required as blocking and wait
stopInstance(inst);
if(_instanceTable->deleteInstance(inst))
{
Info(_log,"Hyperion instance with index '%d' has been deleted", inst);
emit instanceStateChanged(H_DELETED, inst);
emit change();
return true;
}
return false;
}
bool HyperionIManager::saveName(const quint8& inst, const QString& name)
{
if(_instanceTable->saveName(inst, name))
{
emit change();
return true;
}
return false;
}
void HyperionIManager::handleFinished()
{
Hyperion* hyperion = qobject_cast<Hyperion*>(sender());
const quint8 & instance = hyperion->getInstanceIndex();
Info(_log,"Hyperion instance '%s' has been stopped", QSTRING_CSTR(_instanceTable->getNamebyIndex(instance)));
_runningInstances.remove(instance);
hyperion->deleteLater();
emit instanceStateChanged(H_STOPPED, instance);
emit change();
}
void HyperionIManager::handleStarted()
{
Hyperion* hyperion = qobject_cast<Hyperion*>(sender());
const quint8 & instance = hyperion->getInstanceIndex();
Info(_log,"Hyperion instance '%s' has been started", QSTRING_CSTR(_instanceTable->getNamebyIndex(instance)));
_startQueue.removeAll(instance);
_runningInstances.insert(instance, hyperion);
emit instanceStateChanged(H_STARTED, instance);
emit change();
}

View File

@@ -180,8 +180,6 @@ void LinearColorSmoothing::componentStateChange(const hyperion::Components compo
void LinearColorSmoothing::setEnable(bool enable)
{
LedDevice::setEnable(enable);
if (!enable)
{
_timer->stop();

View File

@@ -63,7 +63,7 @@ public:
///
/// @brief select a smoothing cfg given by cfg index from addConfig()
/// @param cfg The index to use
/// @param force Overwrite in any case the current values (used for cfg 0 settings udpate)
/// @param force Overwrite in any case the current values (used for cfg 0 settings update)
///
/// @return On success return else false (and falls back to cfg 0)
///

View File

@@ -22,9 +22,9 @@ PriorityMuxer::PriorityMuxer(int ledCount)
, _activeInputs()
, _lowestPriorityInfo()
, _sourceAutoSelectEnabled(true)
, _updateTimer(new QTimer(this))
, _timer(new QTimer(this))
, _blockTimer(new QTimer(this))
, _updateTimer(new QTimer())
, _timer(new QTimer())
, _blockTimer(new QTimer())
{
// init lowest priority info
_lowestPriorityInfo.priority = PriorityMuxer::LOWEST_PRIORITY;
@@ -43,12 +43,12 @@ PriorityMuxer::PriorityMuxer(int ledCount)
// forward timeRunner signal to prioritiesChanged signal & threading workaround
connect(this, &PriorityMuxer::timeRunner, this, &PriorityMuxer::prioritiesChanged);
connect(this, &PriorityMuxer::signalTimeTrigger, this, &PriorityMuxer::timeTrigger);
connect(this, &PriorityMuxer::activeStateChanged, this, &PriorityMuxer::prioritiesChanged);
// start muxer timer
connect(_updateTimer, &QTimer::timeout, this, &PriorityMuxer::setCurrentTime);
_updateTimer->setInterval(250);
_updateTimer->start();
InputInfo ninfo;
}
PriorityMuxer::~PriorityMuxer()
@@ -279,7 +279,8 @@ void PriorityMuxer::clearAll(bool forceClearAll)
void PriorityMuxer::setCurrentTime(void)
{
const int64_t now = QDateTime::currentMSecsSinceEpoch();
int newPriority = PriorityMuxer::LOWEST_PRIORITY;
int newPriority;
_activeInputs.contains(0) ? newPriority = 0 : newPriority = PriorityMuxer::LOWEST_PRIORITY;
for (auto infoIt = _activeInputs.begin(); infoIt != _activeInputs.end();)
{

View File

@@ -3,6 +3,7 @@
// util
#include <utils/JsonUtils.h>
#include <db/SettingsTable.h>
// json schema process
#include <utils/jsonschema/QJsonFactory.h>
@@ -11,16 +12,13 @@
// write config to filesystem
#include <utils/JsonUtils.h>
// hyperion
#include <hyperion/Hyperion.h>
QJsonObject SettingsManager::schemaJson;
SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, const QString& configFile)
: _hyperion(hyperion)
SettingsManager::SettingsManager(const quint8& instance, QObject* parent)
: QObject(parent)
, _log(Logger::getInstance("SettingsManager"))
, _sTable(new SettingsTable(instance, this))
{
connect(this, &SettingsManager::settingsChanged, _hyperion, &Hyperion::settingsChanged);
// get schema
if(schemaJson.isEmpty())
{
@@ -34,112 +32,78 @@ SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, con
throw std::runtime_error(error.what());
}
}
// get default config
QJsonObject defaultConfig;
if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log))
throw std::runtime_error("Failed to read default config");
Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile));
QJsonSchemaChecker schemaCheckerT;
schemaCheckerT.setSchema(schemaJson);
if(!JsonUtils::readFile(configFile, _qconfig, _log))
throw std::runtime_error("Failed to load config!");
// validate config with schema and correct it if required
QPair<bool, bool> validate = schemaCheckerT.validate(_qconfig);
// errors in schema syntax, abort
if (!validate.second)
// transform json to string lists
QStringList keyList = defaultConfig.keys();
QStringList defValueList;
for(const auto key : keyList)
{
foreach (auto & schemaError, schemaCheckerT.getMessages())
Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError));
throw std::runtime_error("ERROR: Hyperion schema has syntax errors!");
}
// errors in configuration, correct it!
if (!validate.first)
{
Warning(_log,"Errors have been found in the configuration file. Automatic correction has been applied");
_qconfig = schemaCheckerT.getAutoCorrectedConfig(_qconfig);
foreach (auto & schemaError, schemaCheckerT.getMessages())
Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError));
if (!JsonUtils::write(configFile, _qconfig, _log))
throw std::runtime_error("ERROR: Can't save configuration file, aborting");
}
Debug(_log,"Settings database initialized")
}
SettingsManager::SettingsManager(const quint8& instance, const QString& configFile)
: _hyperion(nullptr)
, _log(Logger::getInstance("SettingsManager"))
{
Q_INIT_RESOURCE(resource);
// get schema
if(schemaJson.isEmpty())
{
try
if(defaultConfig[key].isObject())
{
schemaJson = QJsonFactory::readSchema(":/hyperion-schema");
defValueList << QString(QJsonDocument(defaultConfig[key].toObject()).toJson(QJsonDocument::Compact));
}
catch(const std::runtime_error& error)
else if(defaultConfig[key].isArray())
{
throw std::runtime_error(error.what());
defValueList << QString(QJsonDocument(defaultConfig[key].toArray()).toJson(QJsonDocument::Compact));
}
}
// get default config
QJsonObject defaultConfig;
if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log))
throw std::runtime_error("Failed to read default config");
Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile));
QJsonSchemaChecker schemaCheckerT;
schemaCheckerT.setSchema(schemaJson);
if(!JsonUtils::readFile(configFile, _qconfig, _log))
throw std::runtime_error("Failed to load config!");
// validate config with schema and correct it if required
QPair<bool, bool> validate = schemaCheckerT.validate(_qconfig);
// errors in schema syntax, abort
if (!validate.second)
// fill database with default data if required
for(const auto key : keyList)
{
foreach (auto & schemaError, schemaCheckerT.getMessages())
Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError));
throw std::runtime_error("ERROR: Hyperion schema has syntax errors!");
QString val = defValueList.takeFirst();
// prevent overwrite
if(!_sTable->recordExist(key))
_sTable->createSettingsRecord(key,val);
}
// errors in configuration, correct it!
if (!validate.first)
{
Warning(_log,"Errors have been found in the configuration file. Automatic correction has been applied");
_qconfig = schemaCheckerT.getAutoCorrectedConfig(_qconfig);
foreach (auto & schemaError, schemaCheckerT.getMessages())
// need to validate all data in database constuct the entire data object
// TODO refactor schemaChecker to accept QJsonArray in validate(); QJsonDocument container? To validate them per entry...
QJsonObject dbConfig;
for(const auto key : keyList)
{
QJsonDocument doc = _sTable->getSettingsRecord(key);
if(doc.isArray())
dbConfig[key] = doc.array();
else
dbConfig[key] = doc.object();
}
// validate full dbconfig against schema, on error we need to rewrite entire table
QJsonSchemaChecker schemaChecker;
schemaChecker.setSchema(schemaJson);
QPair<bool,bool> valid = schemaChecker.validate(dbConfig);
// check if our main schema syntax is IO
if (!valid.second)
{
foreach (auto & schemaError, schemaChecker.getMessages())
Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError));
throw std::runtime_error("The config schema has invalid syntax. This should never happen! Go fix it!");
}
if (!valid.first)
{
Info(_log,"Table upgrade required...");
dbConfig = schemaChecker.getAutoCorrectedConfig(dbConfig);
foreach (auto & schemaError, schemaChecker.getMessages())
Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError));
if (!JsonUtils::write(configFile, _qconfig, _log))
throw std::runtime_error("ERROR: Can't save configuration file, aborting");
saveSettings(dbConfig);
}
else
_qconfig = dbConfig;
Debug(_log,"Settings database initialized")
}
SettingsManager::~SettingsManager()
{
}
const QJsonDocument SettingsManager::getSetting(const settings::type& type)
{
QString key = settings::typeToString(type);
if(_qconfig[key].isObject())
return QJsonDocument(_qconfig[key].toObject());
else
return QJsonDocument(_qconfig[key].toArray());
return _sTable->getSettingsRecord(settings::typeToString(type));
}
bool SettingsManager::saveSettings(QJsonObject config, const bool& correct)
@@ -161,32 +125,34 @@ bool SettingsManager::saveSettings(QJsonObject config, const bool& correct)
Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError));
}
// save data to file
if(_hyperion != nullptr)
{
if(!JsonUtils::write(_hyperion->getConfigFilePath(), config, _log))
return false;
}
// compare old data with new data to emit/save changes accordingly
for(const auto key : config.keys())
{
QString newData, oldData;
_qconfig[key].isObject()
? oldData = QString(QJsonDocument(_qconfig[key].toObject()).toJson(QJsonDocument::Compact))
: oldData = QString(QJsonDocument(_qconfig[key].toArray()).toJson(QJsonDocument::Compact));
config[key].isObject()
? newData = QString(QJsonDocument(config[key].toObject()).toJson(QJsonDocument::Compact))
: newData = QString(QJsonDocument(config[key].toArray()).toJson(QJsonDocument::Compact));
if(oldData != newData)
emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(newData.toLocal8Bit()));
}
// store the current state
// store the new config
_qconfig = config;
// extract keys and data
QStringList keyList = config.keys();
QStringList newValueList;
for(const auto key : keyList)
{
if(config[key].isObject())
{
newValueList << QString(QJsonDocument(config[key].toObject()).toJson(QJsonDocument::Compact));
}
else if(config[key].isArray())
{
newValueList << QString(QJsonDocument(config[key].toArray()).toJson(QJsonDocument::Compact));
}
}
// compare database data with new data to emit/save changes accordingly
for(const auto key : keyList)
{
QString data = newValueList.takeFirst();
if(_sTable->getSettingsRecordString(key) != data)
{
_sTable->createSettingsRecord(key, data);
emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(data.toLocal8Bit()));
}
}
return true;
}

View File

@@ -63,10 +63,6 @@
{
"$ref": "schema-boblightServer.json"
},
"udpListener" :
{
"$ref": "schema-udpListener.json"
},
"webConfig" :
{
"$ref": "schema-webConfig.json"
@@ -79,6 +75,10 @@
{
"$ref": "schema-instCapture.json"
},
"network":
{
"$ref": "schema-network.json"
},
"ledConfig":
{
"$ref": "schema-ledConfig.json"

View File

@@ -17,11 +17,11 @@
<file alias="schema-flatbufServer.json">schema/schema-flatbufServer.json</file>
<file alias="schema-protoServer.json">schema/schema-protoServer.json</file>
<file alias="schema-boblightServer.json">schema/schema-boblightServer.json</file>
<file alias="schema-udpListener.json">schema/schema-udpListener.json</file>
<file alias="schema-webConfig.json">schema/schema-webConfig.json</file>
<file alias="schema-effects.json">schema/schema-effects.json</file>
<file alias="schema-ledConfig.json">schema/schema-ledConfig.json</file>
<file alias="schema-leds.json">schema/schema-leds.json</file>
<file alias="schema-instCapture.json">schema/schema-instCapture.json</file>
<file alias="schema-network.json">schema/schema-network.json</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,59 @@
{
"type" : "object",
"title" : "edt_conf_net_heading_title",
"required" : true,
"properties" :
{
"apiAuth" :
{
"type" : "boolean",
"title" : "edt_conf_net_apiAuth_title",
"required" : true,
"default" : true,
"propertyOrder" : 1
},
"internetAccessAPI" :
{
"type" : "boolean",
"title" : "edt_conf_net_internetAccessAPI_title",
"required" : true,
"default" : false,
"options": {
"dependencies": {
"apiAuth": true
}
},
"propertyOrder" : 2
},
"ipWhitelist" :
{
"type" : "array",
"title" : "edt_conf_net_ipWhitelist_title",
"required" : true,
"items" : {
"type": "string",
"title" : "edt_conf_net_ip_itemtitle"
},
"options": {
"dependencies": {
"internetAccessAPI": false
}
},
"propertyOrder" : 3
},
"localApiAuth" :
{
"type" : "boolean",
"title" : "edt_conf_net_localApiAuth_title",
"required" : true,
"default" : false,
"options": {
"dependencies": {
"apiAuth": true
}
},
"propertyOrder" : 4
}
},
"additionalProperties" : false
}

View File

@@ -1,56 +0,0 @@
{
"type" : "object",
"title" : "edt_conf_udpl_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : false,
"propertyOrder" : 1
},
"address" :
{
"type" : "string",
"title" : "edt_conf_udpl_address_title",
"default" : "239.255.28.01",
"propertyOrder" : 2
},
"port" :
{
"type" : "integer",
"title" : "edt_conf_general_port_title",
"minimum" : 0,
"maximum" : 65535,
"default" : 2801,
"propertyOrder" : 3
},
"priority" :
{
"type" : "integer",
"title" : "edt_conf_general_priority_title",
"minimum" : 100,
"maximum" : 254,
"default" : 200,
"propertyOrder" : 4
},
"timeout" :
{
"type" : "integer",
"title" : "edt_conf_udpl_timeout_title",
"minimum" : 1000,
"default" : 10000,
"append" : "edt_append_ms",
"propertyOrder" : 5
},
"shared" :
{
"type" : "boolean",
"title" : "edt_conf_udpl_shared_title",
"default" : false,
"propertyOrder" : 6
}
},
"additionalProperties" : false
}

View File

@@ -6,20 +6,16 @@
#include <QTcpSocket>
#include <QHostAddress>
// websocket includes
#include "webserver/WebSocketClient.h"
JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
JsonClientConnection::JsonClientConnection(QTcpSocket *socket, const bool& localConnection)
: QObject()
, _socket(socket)
, _websocketClient(nullptr)
, _receiveBuffer()
, _log(Logger::getInstance("JSONCLIENTCONNECTION"))
{
connect(_socket, &QTcpSocket::disconnected, this, &JsonClientConnection::disconnected);
connect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest);
// create a new instance of JsonAPI
_jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, this);
_jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, localConnection, this);
// get the callback messages from JsonAPI and send it to the client
connect(_jsonAPI,SIGNAL(callbackMessage(QJsonObject)),this,SLOT(sendMessage(QJsonObject)));
}
@@ -27,37 +23,21 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
void JsonClientConnection::readRequest()
{
_receiveBuffer += _socket->readAll();
// might be an old hyperion classic handshake request or raw socket data
if(_receiveBuffer.contains("Upgrade: websocket"))
// raw socket data, handling as usual
int bytes = _receiveBuffer.indexOf('\n') + 1;
while(bytes > 0)
{
if(_websocketClient == Q_NULLPTR)
{
// disconnect this slot from socket for further requests
disconnect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest);
int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19;
QByteArray header(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data());
_websocketClient = new WebSocketClient(header, _socket, this);
}
}
else
{
// raw socket data, handling as usual
int bytes = _receiveBuffer.indexOf('\n') + 1;
while(bytes > 0)
{
// create message string
QString message(QByteArray(_receiveBuffer.data(), bytes));
// create message string
QString message(QByteArray(_receiveBuffer.data(), bytes));
// remove message data from buffer
_receiveBuffer = _receiveBuffer.mid(bytes);
// remove message data from buffer
_receiveBuffer = _receiveBuffer.mid(bytes);
// handle message
_jsonAPI->handleMessage(message);
// handle message
_jsonAPI->handleMessage(message);
// try too look up '\n' again
bytes = _receiveBuffer.indexOf('\n') + 1;
}
// try too look up '\n' again
bytes = _receiveBuffer.indexOf('\n') + 1;
}
}

View File

@@ -10,7 +10,6 @@
class JsonAPI;
class QTcpSocket;
class WebSocketClient;
///
/// The Connection object created by \a JsonServer when a new connection is established
@@ -24,7 +23,7 @@ public:
/// Constructor
/// @param socket The Socket object for this connection
///
JsonClientConnection(QTcpSocket * socket);
JsonClientConnection(QTcpSocket * socket, const bool& localConnection);
signals:
void connectionClosed();
@@ -42,7 +41,6 @@ private slots:
private:
QTcpSocket* _socket;
WebSocketClient* _websocketClient;
/// new instance of JsonAPI
JsonAPI * _jsonAPI;

View File

@@ -7,6 +7,7 @@
// bonjour include
#include <bonjour/bonjourserviceregister.h>
#include <utils/NetOrigin.h>
// qt includes
#include <QTcpServer>
@@ -19,6 +20,7 @@ JsonServer::JsonServer(const QJsonDocument& config)
, _server(new QTcpServer(this))
, _openConnections()
, _log(Logger::getInstance("JSONSERVER"))
, _netOrigin(NetOrigin::getInstance())
{
Debug(_log, "Created instance");
@@ -95,12 +97,17 @@ void JsonServer::newConnection()
{
if (QTcpSocket * socket = _server->nextPendingConnection())
{
Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str());
JsonClientConnection * connection = new JsonClientConnection(socket);
_openConnections.insert(connection);
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
{
Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str());
JsonClientConnection * connection = new JsonClientConnection(socket, _netOrigin->isLocalAddress(socket->peerAddress(), socket->localAddress()));
_openConnections.insert(connection);
// register slot for cleaning up after the connection closed
connect(connection, &JsonClientConnection::connectionClosed, this, &JsonServer::closedConnection);
// register slot for cleaning up after the connection closed
connect(connection, &JsonClientConnection::connectionClosed, this, &JsonServer::closedConnection);
}
else
socket->close();
}
}
}

View File

@@ -10,8 +10,6 @@
#include "hyperion/Hyperion.h"
#include <utils/JsonUtils.h>
#include <QDebug>
LedDevice::LedDevice(const QJsonObject& config, QObject* parent)
: QObject(parent)
, _devConfig(config)

View File

@@ -53,7 +53,7 @@ bool ProviderUdp::init(const QJsonObject &deviceConfig)
}
_port = deviceConfig["port"].toInt(_port);
if ( (_port <= 0) || (_port > 65535) )
if ( (_port <= 0) || (_port > MAX_PORT) )
{
throw std::runtime_error("invalid target port");
}

View File

@@ -9,6 +9,8 @@
class QUdpSocket;
#define MAX_PORT 65535
///
/// The ProviderUdp implements an abstract base-class for LedDevices using UDP packets.
///
@@ -54,6 +56,6 @@ protected:
///
QUdpSocket * _udpSocket;
QHostAddress _address;
quint16 _port;
ushort _port;
QString _defaultHost;
};

View File

@@ -7,10 +7,7 @@
#include <QTimer>
#include <QRgb>
// Hyperion includes
#include <hyperion/Hyperion.h>
// TODO Remove this class if third-party apps have been migrated (eg. Hyperion Android Gabber, Windows Screen grabber etc.)
ProtoClientConnection::ProtoClientConnection(QTcpSocket* socket, const int &timeout, QObject *parent)
: QObject(parent)
@@ -20,7 +17,6 @@ ProtoClientConnection::ProtoClientConnection(QTcpSocket* socket, const int &time
, _timeoutTimer(new QTimer(this))
, _timeout(timeout * 1000)
, _priority()
, _hyperion(Hyperion::getInstance())
{
// timer setup
_timeoutTimer->setSingleShot(true);
@@ -77,8 +73,8 @@ void ProtoClientConnection::forceClose()
void ProtoClientConnection::disconnected()
{
Debug(_log, "Socket Closed");
_socket->deleteLater();
_hyperion->clear(_priority);
_socket->deleteLater();
emit clearGlobalInput(_priority);
emit clientDisconnected();
}
@@ -128,16 +124,22 @@ void ProtoClientConnection::handleColorCommand(const proto::ColorRequest &messag
color.green = qGreen(message.rgbcolor());
color.blue = qBlue(message.rgbcolor());
if (priority < 100 || priority >= 200)
{
sendErrorReply("The priority " + std::to_string(priority) + " is not in the priority range between 100 and 199.");
return;
}
// make sure the prio is registered before setColor()
if(priority != _priority)
{
_hyperion->clear(_priority);
_hyperion->registerInput(priority, hyperion::COMP_PROTOSERVER, "Proto@"+_clientAddress);
emit clearGlobalInput(_priority);
emit registerGlobalInput(priority, hyperion::COMP_PROTOSERVER, "Proto@"+_clientAddress);
_priority = priority;
}
// set output
_hyperion->setColor(_priority, color, duration);
emit setGlobalInputColor(_priority, color, duration);
// send reply
sendSuccessReply();
@@ -152,11 +154,17 @@ void ProtoClientConnection::handleImageCommand(const proto::ImageRequest &messag
int height = message.imageheight();
const std::string & imageData = message.imagedata();
if (priority < 100 || priority >= 200)
{
sendErrorReply("The priority " + std::to_string(priority) + " is not in the priority range between 100 and 199.");
return;
}
// make sure the prio is registered before setInput()
if(priority != _priority)
{
_hyperion->clear(_priority);
_hyperion->registerInput(priority, hyperion::COMP_PROTOSERVER, "Proto@"+_clientAddress);
emit clearGlobalInput(_priority);
emit registerGlobalInput(priority, hyperion::COMP_PROTOSERVER, "Proto@"+_clientAddress);
_priority = priority;
}
@@ -171,7 +179,7 @@ void ProtoClientConnection::handleImageCommand(const proto::ImageRequest &messag
Image<ColorRgb> image(width, height);
memcpy(image.memptr(), imageData.c_str(), imageData.size());
_hyperion->setInputImage(_priority, image, duration);
emit setGlobalInputImage(_priority, image, duration);
// send reply
sendSuccessReply();
@@ -184,15 +192,15 @@ void ProtoClientConnection::handleClearCommand(const proto::ClearRequest &messag
int priority = message.priority();
// clear priority
_hyperion->clear(priority);
emit clearGlobalInput(priority);
// send reply
sendSuccessReply();
}
void ProtoClientConnection::handleClearallCommand()
{
// clear priority
_hyperion->clearall();
// clear all priority
emit clearAllGlobalInput();
// send reply
sendSuccessReply();

View File

@@ -11,10 +11,9 @@
class QTcpSocket;
class QTimer;
class Hyperion;
namespace proto {
class HyperionRequest;
class HyperionRequest;
}
///
@@ -34,12 +33,42 @@ public:
explicit ProtoClientConnection(QTcpSocket* socket, const int &timeout, QObject *parent);
signals:
///
/// @brief forward register data to HyperionDaemon
///
void registerGlobalInput(const int priority, const hyperion::Components& component, const QString& origin = "ProtoBuffer", const QString& owner = "", unsigned smooth_cfg = 0);
///
/// @brief Forward clear command to HyperionDaemon
///
void clearGlobalInput(const int priority);
///
/// @brief Forward clearAll command to HyperionDaemon
///
void clearAllGlobalInput(bool forceClearAll=false);
///
/// @brief forward prepared image to HyperionDaemon
///
const bool setGlobalInputImage(const int priority, const Image<ColorRgb>& image, const int timeout_ms, const bool& clearEffect = false);
///
/// @brief Forward requested color
///
void setGlobalInputColor(const int priority, const ColorRgb &ledColor, const int timeout_ms, const QString& origin = "ProtoBuffer" ,bool clearEffects = true);
///
/// @brief Emits whenever the client disconnected
///
void clientDisconnected();
public slots:
///
/// @brief Requests a registration from the client
///
void registationRequired(const int priority) { if (_priority == priority) _priority = -1; };
///
/// @brief close the socket and call disconnected()
///
@@ -127,9 +156,6 @@ private:
int _timeout;
int _priority;
/// Link to Hyperion for writing led-values to a priority channel
Hyperion* _hyperion;
/// The buffer used for reading data from the socket
QByteArray _receiveBuffer;
};

View File

@@ -1,6 +1,10 @@
#include <protoserver/ProtoServer.h>
#include "ProtoClientConnection.h"
// util
#include <utils/NetOrigin.h>
#include <utils/GlobalSignals.h>
// qt
#include <QJsonObject>
#include <QTcpServer>
@@ -24,6 +28,7 @@ ProtoServer::~ProtoServer()
void ProtoServer::initServer()
{
_netOrigin = NetOrigin::getInstance();
connect(_server, &QTcpServer::newConnection, this, &ProtoServer::newConnection);
// apply config
@@ -58,11 +63,22 @@ void ProtoServer::newConnection()
{
if(QTcpSocket * socket = _server->nextPendingConnection())
{
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
ProtoClientConnection * client = new ProtoClientConnection(socket, _timeout, this);
// internal
connect(client, &ProtoClientConnection::clientDisconnected, this, &ProtoServer::clientDisconnected);
_openConnections.append(client);
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
{
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
ProtoClientConnection * client = new ProtoClientConnection(socket, _timeout, this);
// internal
connect(client, &ProtoClientConnection::clientDisconnected, this, &ProtoServer::clientDisconnected);
connect(client, &ProtoClientConnection::registerGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::registerGlobalInput);
connect(client, &ProtoClientConnection::clearGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::clearGlobalInput);
connect(client, &ProtoClientConnection::clearAllGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::clearAllGlobalInput);
connect(client, &ProtoClientConnection::setGlobalInputImage, GlobalSignals::getInstance(), &GlobalSignals::setGlobalImage);
connect(client, &ProtoClientConnection::setGlobalInputColor, GlobalSignals::getInstance(), &GlobalSignals::setGlobalColor);
connect(GlobalSignals::getInstance(), &GlobalSignals::globalRegRequired, client, &ProtoClientConnection::registationRequired);
_openConnections.append(client);
}
else
socket->close();
}
}
}

View File

@@ -4,6 +4,7 @@
#include "SSDPDescription.h"
#include <hyperion/Hyperion.h>
#include <HyperionConfig.h>
#include <hyperion/AuthManager.h>
#include <QNetworkInterface>
#include <QNetworkConfigurationManager>
@@ -140,5 +141,5 @@ const QString SSDPHandler::buildDesc()
/// %2 friendly name Hyperion 2.0.0 (192.168.0.177)
/// %3 modelNumber 2.0.0
/// %4 serialNumber / UDN (H ID) Fjsa723dD0....
return SSDP_DESCRIPTION.arg(getBaseAddress(), QString("Hyperion (%2)").arg(_localAddress), QString(HYPERION_VERSION), Hyperion::getInstance()->getId());
return SSDP_DESCRIPTION.arg(getBaseAddress(), QString("Hyperion (%2)").arg(_localAddress), QString(HYPERION_VERSION), AuthManager::getInstance()->getID());
}

View File

@@ -1,10 +1,14 @@
#include <ssdp/SSDPServer.h>
// util
// utils
#include <utils/SysInfo.h>
#include <hyperion/Hyperion.h>
// Hyperion
#include <HyperionConfig.h>
// auth manager
#include <hyperion/AuthManager.h>
#include <QUdpSocket>
#include <QDateTime>
@@ -96,7 +100,7 @@ void SSDPServer::initServer()
_serverHeader = data.prettyName+"/"+data.productVersion+" UPnP/1.0 Hyperion/"+QString(HYPERION_VERSION);
// usn uuid
_uuid = Hyperion::getInstance()->getId();
_uuid = AuthManager::getInstance()->getID();
connect(_udpSocket, &QUdpSocket::readyRead, this, &SSDPServer::readPendingDatagrams);
}

View File

@@ -1,14 +0,0 @@
# Define the current source locations
set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/udplistener)
set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/udplistener)
FILE ( GLOB UDPListener_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
add_library(udplistener ${UDPListener_SOURCES} )
target_link_libraries(udplistener
hyperion
hyperion-utils
${QT_LIBRARIES}
)

View File

@@ -1,159 +0,0 @@
// project includes
#include <udplistener/UDPListener.h>
// bonjour includes
#include <bonjour/bonjourserviceregister.h>
// hyperion includes
#include <hyperion/Hyperion.h>
#include "HyperionConfig.h"
// qt includes
#include <QUdpSocket>
#include <QJsonObject>
using namespace hyperion;
UDPListener::UDPListener(const QJsonDocument& config) :
QObject(),
_server(new QUdpSocket(this)),
_priority(0),
_timeout(0),
_log(Logger::getInstance("UDPLISTENER")),
_isActive(false),
_listenPort(0)
{
// listen for component change
connect(Hyperion::getInstance(), &Hyperion::componentStateChanged, this, &UDPListener::componentStateChanged);
// init
handleSettingsUpdate(settings::UDPLISTENER, config);
}
UDPListener::~UDPListener()
{
// clear the current channel
stop();
delete _server;
}
void UDPListener::start()
{
if ( active() )
return;
QHostAddress mcastGroup;
if (_listenAddress.isInSubnet(QHostAddress::parseSubnet("224.0.0.0/4"))) {
mcastGroup = _listenAddress;
}
if (!_server->bind(_listenAddress, _listenPort, _bondage))
{
Error(_log, "Could not bind to %s:%d", _listenAddress.toString().toStdString().c_str(), _listenPort);
}
else
{
Info(_log, "Started, listening on %s:%d", _listenAddress.toString().toStdString().c_str(), _listenPort);
if (!mcastGroup.isNull()) {
bool joinGroupOK = _server->joinMulticastGroup(_listenAddress);
InfoIf ( joinGroupOK, _log, "Multicast enabled");
WarningIf( ! joinGroupOK, _log, "Multicast failed");
}
_isActive = true;
if(_serviceRegister == nullptr)
{
_serviceRegister = new BonjourServiceRegister(this);
_serviceRegister->registerService("_hyperiond-udp._udp", _listenPort);
}
else if( _serviceRegister->getPort() != _listenPort)
{
delete _serviceRegister;
_serviceRegister = new BonjourServiceRegister(this);
_serviceRegister->registerService("_hyperiond-udp._udp", _listenPort);
}
}
Hyperion::getInstance()->getComponentRegister().componentStateChanged(COMP_UDPLISTENER, _isActive);
}
void UDPListener::stop()
{
if ( ! active() )
return;
_server->close();
_isActive = false;
Info(_log, "Stopped");
Hyperion::getInstance()->getComponentRegister().componentStateChanged(COMP_UDPLISTENER, _isActive);
}
void UDPListener::componentStateChanged(const hyperion::Components component, bool enable)
{
if (component == COMP_UDPLISTENER)
{
if (_isActive != enable)
{
if (enable) start();
else stop();
}
}
}
uint16_t UDPListener::getPort() const
{
return _server->localPort();
}
void UDPListener::readPendingDatagrams()
{
while (_server->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(_server->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
_server->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
processTheDatagram(&datagram, &sender);
}
}
void UDPListener::processTheDatagram(const QByteArray * datagram, const QHostAddress * sender)
{
int packetLedCount = datagram->size()/3;
//DebugIf( (packetLedCount != hyperionLedCount), _log, "packetLedCount (%d) != hyperionLedCount (%d)", packetLedCount, hyperionLedCount);
std::vector<ColorRgb> _ledColors(packetLedCount, ColorRgb::BLACK);
for (int ledIndex=0; ledIndex < packetLedCount; ledIndex++) {
ColorRgb & rgb = _ledColors[ledIndex];
rgb.red = datagram->at(ledIndex*3+0);
rgb.green = datagram->at(ledIndex*3+1);
rgb.blue = datagram->at(ledIndex*3+2);
}
// TODO provide a setInput with origin arg to overwrite senders smarter
emit registerGlobalInput(_priority, hyperion::COMP_UDPLISTENER, QString("UDPListener@%1").arg(sender->toString()));
emit setGlobalInput(_priority, _ledColors, _timeout);
}
void UDPListener::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
{
if(type == settings::UDPLISTENER)
{
QJsonObject obj = config.object();
// if we change the prio we need to make sure the old one is cleared before we apply the new one!
stop();
QString addr = obj["address"].toString("");
_priority = obj["priority"].toInt();
_listenPort = obj["port"].toInt();
_listenAddress = addr.isEmpty()? QHostAddress::AnyIPv4 : QHostAddress(addr);
_bondage = (obj["shared"].toBool(false)) ? QAbstractSocket::ShareAddress : QAbstractSocket::DefaultForPlatform;
_timeout = obj["timeout"].toInt(10000);
if(obj["enable"].toBool())
start();
}
}

View File

@@ -1 +0,0 @@

View File

@@ -95,12 +95,6 @@ namespace FileUtils {
return true;
}
QString convertPath(const QString path)
{
QString p = path;
return p.replace(QString("$ROOT"), Hyperion::getInstance()->getRootPath());
}
void resolveFileError(const QFile& file, Logger* log)
{
QFile::FileError error = file.error();

View File

@@ -0,0 +1,76 @@
#include <utils/NetOrigin.h>
#include <QJsonObject>
NetOrigin* NetOrigin::instance = nullptr;
NetOrigin::NetOrigin(QObject* parent, Logger* log)
: QObject(parent)
, _log(log)
, _internetAccessAllowed(false)
, _ipWhitelist()
{
NetOrigin::instance = this;
}
bool NetOrigin::accessAllowed(const QHostAddress& address, const QHostAddress& local)
{
if(_internetAccessAllowed)
return true;
if(_ipWhitelist.contains(address)) // v4 and v6
return true;
if(!isLocalAddress(address, local))
{
Warning(_log,"Client connection with IP address '%s' has been rejected! It's not whitelisted, access denied.",QSTRING_CSTR(address.toString()));
return false;
}
return true;
}
bool NetOrigin::isLocalAddress(const QHostAddress& address, const QHostAddress& local)
{
if(address.protocol() == QAbstractSocket::IPv4Protocol)
{
if(!address.isInSubnet(local, 24)) // 255.255.255.xxx; IPv4 0-32
{
return false;
}
}
else if(address.protocol() == QAbstractSocket::IPv6Protocol)
{
if(!address.isInSubnet(local, 64)) // 2001:db8:abcd:0012:XXXX:XXXX:XXXX:XXXX; IPv6 0-128
{
return false;
}
}
return true;
}
void NetOrigin::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
{
if(type == settings::NETWORK)
{
const QJsonObject& obj = config.object();
_internetAccessAllowed = obj["internetAccessAPI"].toBool(false);
const QJsonArray& arr = obj["ipWhitelist"].toArray();
_ipWhitelist.clear();
for(const auto& e : arr)
{
const QString& entry = e.toString("");
if(entry.isEmpty())
continue;
QHostAddress host(entry);
if(host.isNull())
{
Warning(_log,"The whitelisted IP address '%s' isn't valid! Skipped",QSTRING_CSTR(entry));
continue;
}
_ipWhitelist << host;
}
}
}

View File

@@ -55,7 +55,6 @@ void CgiHandler::cmd_cfg_jsonserver()
if ( _args.at(0) == "cfg_jsonserver" )
{
quint16 jsonPort = 19444;
// send result as reply
_reply->addHeader ("Content-Type", "text/plain" );
_reply->appendRawData (QByteArrayLiteral(":") % QString::number(jsonPort).toUtf8() );

View File

@@ -4,8 +4,8 @@
#include "QtHttpReply.h"
#include "QtHttpServer.h"
#include "QtHttpHeader.h"
#include "WebSocketClient.h"
#include "WebJsonRpc.h"
#include "webserver/WebSocketClient.h"
#include <QCryptographicHash>
#include <QTcpSocket>
@@ -16,103 +16,142 @@
const QByteArray & QtHttpClientWrapper::CRLF = QByteArrayLiteral ("\r\n");
QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent)
: QObject (parent)
, m_guid ("")
, m_parsingStatus (AwaitingRequest)
, m_sockClient (sock)
, m_currentRequest (Q_NULLPTR)
, m_serverHandle (parent)
, m_websocketClient(nullptr)
, m_webJsonRpc (nullptr)
QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, const bool& localConnection, QtHttpServer * parent)
: QObject (parent)
, m_guid ("")
, m_parsingStatus (AwaitingRequest)
, m_sockClient (sock)
, m_currentRequest (Q_NULLPTR)
, m_serverHandle (parent)
, m_localConnection(localConnection)
, m_websocketClient(nullptr)
, m_webJsonRpc (nullptr)
{
connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
}
QString QtHttpClientWrapper::getGuid (void) {
if (m_guid.isEmpty ()) {
m_guid = QString::fromLocal8Bit (
QCryptographicHash::hash (
QByteArray::number ((quint64) (this)),
QCryptographicHash::Md5
).toHex ()
);
}
return m_guid;
QString QtHttpClientWrapper::getGuid (void)
{
if (m_guid.isEmpty ())
{
m_guid = QString::fromLocal8Bit (
QCryptographicHash::hash (
QByteArray::number ((quint64) (this)),
QCryptographicHash::Md5
).toHex ()
);
}
return m_guid;
}
void QtHttpClientWrapper::onClientDataReceived (void) {
if (m_sockClient != Q_NULLPTR) {
while (m_sockClient->bytesAvailable ()) {
QByteArray line = m_sockClient->readLine ();
switch (m_parsingStatus) { // handle parsing steps
case AwaitingRequest: { // "command url version" × 1
QString str = QString::fromUtf8 (line).trimmed ();
QStringList parts = str.split (SPACE, QString::SkipEmptyParts);
if (parts.size () == 3) {
QString command = parts.at (0);
QString url = parts.at (1);
QString version = parts.at (2);
if (version == QtHttpServer::HTTP_VERSION) {
m_currentRequest = new QtHttpRequest (this, m_serverHandle);
m_currentRequest->setClientInfo(m_sockClient->localAddress(), m_sockClient->peerAddress());
m_currentRequest->setUrl (QUrl (url));
m_currentRequest->setCommand (command);
m_parsingStatus = AwaitingHeaders;
}
else {
m_parsingStatus = ParsingError;
//qWarning () << "Error : unhandled HTTP version :" << version;
}
}
else {
m_parsingStatus = ParsingError;
//qWarning () << "Error : incorrect HTTP command line :" << line;
}
break;
}
case AwaitingHeaders: { // "header: value" × N (until empty line)
QByteArray raw = line.trimmed ();
if (!raw.isEmpty ()) { // parse headers
int pos = raw.indexOf (COLON);
if (pos > 0) {
QByteArray header = raw.left (pos).trimmed ();
QByteArray value = raw.mid (pos +1).trimmed ();
m_currentRequest->addHeader (header, value);
if (header == QtHttpHeader::ContentLength) {
bool ok = false;
const int len = value.toInt (&ok, 10);
if (ok) {
m_currentRequest->addHeader (QtHttpHeader::ContentLength, QByteArray::number (len));
}
}
}
else {
m_parsingStatus = ParsingError;
qWarning () << "Error : incorrect HTTP headers line :" << line;
}
}
else { // end of headers
if (m_currentRequest->getHeader (QtHttpHeader::ContentLength).toInt () > 0) {
m_parsingStatus = AwaitingContent;
}
else {
m_parsingStatus = RequestParsed;
}
}
break;
}
case AwaitingContent: { // raw data × N (until EOF ??)
m_currentRequest->appendRawData (line);
if (m_currentRequest->getRawDataSize () == m_currentRequest->getHeader (QtHttpHeader::ContentLength).toInt ()) {
m_parsingStatus = RequestParsed;
}
break;
}
default: { break; }
}
switch (m_parsingStatus) { // handle parsing status end/error
case RequestParsed: { // a valid request has ben fully parsed
void QtHttpClientWrapper::onClientDataReceived (void)
{
if (m_sockClient != Q_NULLPTR)
{
while (m_sockClient->bytesAvailable ())
{
QByteArray line = m_sockClient->readLine ();
switch (m_parsingStatus) // handle parsing steps
{
case AwaitingRequest: // "command url version" × 1
{
QString str = QString::fromUtf8 (line).trimmed ();
QStringList parts = str.split (SPACE, QString::SkipEmptyParts);
if (parts.size () == 3)
{
QString command = parts.at (0);
QString url = parts.at (1);
QString version = parts.at (2);
if (version == QtHttpServer::HTTP_VERSION)
{
m_currentRequest = new QtHttpRequest (this, m_serverHandle);
m_currentRequest->setClientInfo(m_sockClient->localAddress(), m_sockClient->peerAddress());
m_currentRequest->setUrl (QUrl (url));
m_currentRequest->setCommand (command);
m_parsingStatus = AwaitingHeaders;
}
else
{
m_parsingStatus = ParsingError;
//qWarning () << "Error : unhandled HTTP version :" << version;
}
}
else
{
m_parsingStatus = ParsingError;
//qWarning () << "Error : incorrect HTTP command line :" << line;
}
break;
}
case AwaitingHeaders: // "header: value" × N (until empty line)
{
QByteArray raw = line.trimmed ();
if (!raw.isEmpty ()) // parse headers
{
int pos = raw.indexOf (COLON);
if (pos > 0)
{
QByteArray header = raw.left (pos).trimmed ();
QByteArray value = raw.mid (pos +1).trimmed ();
m_currentRequest->addHeader (header, value);
if (header == QtHttpHeader::ContentLength)
{
bool ok = false;
const int len = value.toInt (&ok, 10);
if (ok)
{
m_currentRequest->addHeader (QtHttpHeader::ContentLength, QByteArray::number (len));
}
}
}
else
{
m_parsingStatus = ParsingError;
qWarning () << "Error : incorrect HTTP headers line :" << line;
}
}
else // end of headers
{
if (m_currentRequest->getHeader (QtHttpHeader::ContentLength).toInt () > 0)
{
m_parsingStatus = AwaitingContent;
}
else
{
m_parsingStatus = RequestParsed;
}
}
break;
}
case AwaitingContent: // raw data × N (until EOF ??)
{
m_currentRequest->appendRawData (line);
if (m_currentRequest->getRawDataSize () == m_currentRequest->getHeader (QtHttpHeader::ContentLength).toInt ())
{
m_parsingStatus = RequestParsed;
}
break;
}
default:
{
break;
}
}
switch (m_parsingStatus) // handle parsing status end/error
{
case RequestParsed: // a valid request has ben fully parsed
{
// Catch websocket header "Upgrade"
if(m_currentRequest->getHeader(QtHttpHeader::Upgrade) == "websocket")
{
@@ -120,146 +159,184 @@ void QtHttpClientWrapper::onClientDataReceived (void) {
{
// disconnect this slot from socket for further requests
disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
m_websocketClient = new WebSocketClient(m_currentRequest->getHeader(QtHttpHeader::SecWebSocketKey), m_sockClient, this);
// disabling packet bunching
m_sockClient->setSocketOption(QAbstractSocket::LowDelayOption, 1);
m_sockClient->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, m_localConnection, this);
}
break;
}
// add post data to request and catch /jsonrpc subroute url
if ( m_currentRequest->getCommand() == "POST")
{
QtHttpPostData postData;
QByteArray data = m_currentRequest->getRawData();
QList<QByteArray> parts = data.split('&');
for (int i = 0; i < parts.size(); ++i)
{
QList<QByteArray> keyValue = parts.at(i).split('=');
QByteArray value;
if (keyValue.size()>1)
{
value = QByteArray::fromPercentEncoding(keyValue.at(1));
}
postData.insert(QString::fromUtf8(keyValue.at(0)),value);
}
m_currentRequest->setPostData(postData);
if ( m_currentRequest->getCommand() == "POST")
{
QtHttpPostData postData;
QByteArray data = m_currentRequest->getRawData();
QList<QByteArray> parts = data.split('&');
for (int i = 0; i < parts.size(); ++i)
{
QList<QByteArray> keyValue = parts.at(i).split('=');
QByteArray value;
if (keyValue.size()>1)
{
value = QByteArray::fromPercentEncoding(keyValue.at(1));
}
postData.insert(QString::fromUtf8(keyValue.at(0)),value);
}
m_currentRequest->setPostData(postData);
// catch /jsonrpc in url, we need async callback, StaticFileServing is sync
QString path = m_currentRequest->getUrl ().path ();
QStringList uri_parts = path.split('/', QString::SkipEmptyParts);
if ( ! uri_parts.empty() && uri_parts.at(0) == "json-rpc" )
{
if(m_webJsonRpc == Q_NULLPTR)
{
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, this);
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, m_localConnection, this);
}
m_webJsonRpc->handleMessage(m_currentRequest);
break;
}
}
}
QtHttpReply reply (m_serverHandle);
connect (&reply, &QtHttpReply::requestSendHeaders,
this, &QtHttpClientWrapper::onReplySendHeadersRequested);
connect (&reply, &QtHttpReply::requestSendData,
this, &QtHttpClientWrapper::onReplySendDataRequested);
emit m_serverHandle->requestNeedsReply (m_currentRequest, &reply); // allow app to handle request
QtHttpReply reply (m_serverHandle);
connect (&reply, &QtHttpReply::requestSendHeaders, this, &QtHttpClientWrapper::onReplySendHeadersRequested);
connect (&reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested);
emit m_serverHandle->requestNeedsReply (m_currentRequest, &reply); // allow app to handle request
m_parsingStatus = sendReplyToClient (&reply);
break;
}
case ParsingError: { // there was an error durin one of parsing steps
m_sockClient->readAll (); // clear remaining buffer to ignore content
QtHttpReply reply (m_serverHandle);
reply.setStatusCode (QtHttpReply::BadRequest);
reply.appendRawData (QByteArrayLiteral ("<h1>Bad Request (HTTP parsing error) !</h1>"));
reply.appendRawData (CRLF);
m_parsingStatus = sendReplyToClient (&reply);
break;
}
default: { break; }
}
}
}
break;
}
case ParsingError: // there was an error durin one of parsing steps
{
m_sockClient->readAll (); // clear remaining buffer to ignore content
QtHttpReply reply (m_serverHandle);
reply.setStatusCode (QtHttpReply::BadRequest);
reply.appendRawData (QByteArrayLiteral ("<h1>Bad Request (HTTP parsing error) !</h1>"));
reply.appendRawData (CRLF);
m_parsingStatus = sendReplyToClient (&reply);
break;
}
default:
{
break;
}
}
}
}
}
void QtHttpClientWrapper::onReplySendHeadersRequested (void) {
QtHttpReply * reply = qobject_cast<QtHttpReply *> (sender ());
if (reply != Q_NULLPTR) {
QByteArray data;
// HTTP Version + Status Code + Status Msg
data.append (QtHttpServer::HTTP_VERSION);
data.append (SPACE);
data.append (QByteArray::number (reply->getStatusCode ()));
data.append (SPACE);
data.append (QtHttpReply::getStatusTextForCode (reply->getStatusCode ()));
data.append (CRLF);
// Header name: header value
if (reply->useChunked ()) {
static const QByteArray & CHUNKED = QByteArrayLiteral ("chunked");
reply->addHeader (QtHttpHeader::TransferEncoding, CHUNKED);
}
else {
reply->addHeader (QtHttpHeader::ContentLength, QByteArray::number (reply->getRawDataSize ()));
}
const QList<QByteArray> & headersList = reply->getHeadersList ();
foreach (const QByteArray & header, headersList) {
data.append (header);
data.append (COLON);
data.append (SPACE);
data.append (reply->getHeader (header));
data.append (CRLF);
}
// empty line
data.append (CRLF);
m_sockClient->write (data);
m_sockClient->flush ();
}
void QtHttpClientWrapper::onReplySendHeadersRequested (void)
{
QtHttpReply * reply = qobject_cast<QtHttpReply *> (sender ());
if (reply != Q_NULLPTR)
{
QByteArray data;
// HTTP Version + Status Code + Status Msg
data.append (QtHttpServer::HTTP_VERSION);
data.append (SPACE);
data.append (QByteArray::number (reply->getStatusCode ()));
data.append (SPACE);
data.append (QtHttpReply::getStatusTextForCode (reply->getStatusCode ()));
data.append (CRLF);
if (reply->useChunked ()) // Header name: header value
{
static const QByteArray & CHUNKED = QByteArrayLiteral ("chunked");
reply->addHeader (QtHttpHeader::TransferEncoding, CHUNKED);
}
else
{
reply->addHeader (QtHttpHeader::ContentLength, QByteArray::number (reply->getRawDataSize ()));
}
const QList<QByteArray> & headersList = reply->getHeadersList ();
foreach (const QByteArray & header, headersList)
{
data.append (header);
data.append (COLON);
data.append (SPACE);
data.append (reply->getHeader (header));
data.append (CRLF);
}
// empty line
data.append (CRLF);
m_sockClient->write (data);
m_sockClient->flush ();
}
}
void QtHttpClientWrapper::onReplySendDataRequested (void) {
QtHttpReply * reply = qobject_cast<QtHttpReply *> (sender ());
if (reply != Q_NULLPTR) {
// content raw data
QByteArray data = reply->getRawData ();
if (reply->useChunked ()) {
data.prepend (QByteArray::number (data.size (), 16) % CRLF);
data.append (CRLF);
reply->resetRawData ();
}
// write to socket
m_sockClient->write (data);
m_sockClient->flush ();
}
void QtHttpClientWrapper::onReplySendDataRequested (void)
{
QtHttpReply * reply = qobject_cast<QtHttpReply *> (sender ());
if (reply != Q_NULLPTR)
{
// content raw data
QByteArray data = reply->getRawData ();
if (reply->useChunked ())
{
data.prepend (QByteArray::number (data.size (), 16) % CRLF);
data.append (CRLF);
reply->resetRawData ();
}
// write to socket
m_sockClient->write (data);
m_sockClient->flush ();
}
}
void QtHttpClientWrapper::sendToClientWithReply(QtHttpReply * reply) {
connect (reply, &QtHttpReply::requestSendHeaders,
this, &QtHttpClientWrapper::onReplySendHeadersRequested);
connect (reply, &QtHttpReply::requestSendData,
this, &QtHttpClientWrapper::onReplySendDataRequested);
void QtHttpClientWrapper::sendToClientWithReply(QtHttpReply * reply)
{
connect (reply, &QtHttpReply::requestSendHeaders, this, &QtHttpClientWrapper::onReplySendHeadersRequested);
connect (reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested);
m_parsingStatus = sendReplyToClient (reply);
}
QtHttpClientWrapper::ParsingStatus QtHttpClientWrapper::sendReplyToClient (QtHttpReply * reply) {
if (reply != Q_NULLPTR) {
if (!reply->useChunked ()) {
//reply->appendRawData (CRLF);
// send all headers and all data in one shot
reply->requestSendHeaders ();
reply->requestSendData ();
}
else {
// last chunk
m_sockClient->write ("0" % CRLF % CRLF);
m_sockClient->flush ();
}
if (m_currentRequest != Q_NULLPTR) {
static const QByteArray & CLOSE = QByteArrayLiteral ("close");
if (m_currentRequest->getHeader (QtHttpHeader::Connection).toLower () == CLOSE) {
// must close connection after this request
m_sockClient->close ();
}
m_currentRequest->deleteLater ();
m_currentRequest = Q_NULLPTR;
}
}
return AwaitingRequest;
QtHttpClientWrapper::ParsingStatus QtHttpClientWrapper::sendReplyToClient (QtHttpReply * reply)
{
if (reply != Q_NULLPTR)
{
if (!reply->useChunked ())
{
//reply->appendRawData (CRLF);
// send all headers and all data in one shot
reply->requestSendHeaders ();
reply->requestSendData ();
}
else
{
// last chunk
m_sockClient->write ("0" % CRLF % CRLF);
m_sockClient->flush ();
}
if (m_currentRequest != Q_NULLPTR)
{
static const QByteArray & CLOSE = QByteArrayLiteral ("close");
if (m_currentRequest->getHeader (QtHttpHeader::Connection).toLower () == CLOSE)
{
// must close connection after this request
m_sockClient->close ();
}
m_currentRequest->deleteLater ();
m_currentRequest = Q_NULLPTR;
}
}
return AwaitingRequest;
}

View File

@@ -13,43 +13,44 @@ class WebSocketClient;
class WebJsonRpc;
class QtHttpClientWrapper : public QObject {
Q_OBJECT
Q_OBJECT
public:
explicit QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent);
explicit QtHttpClientWrapper (QTcpSocket * sock, const bool& localConnection, QtHttpServer * parent);
static const char SPACE = ' ';
static const char COLON = ':';
static const QByteArray & CRLF;
static const char SPACE = ' ';
static const char COLON = ':';
static const QByteArray & CRLF;
enum ParsingStatus {
ParsingError = -1,
AwaitingRequest = 0,
AwaitingHeaders = 1,
AwaitingContent = 2,
RequestParsed = 3
};
enum ParsingStatus {
ParsingError = -1,
AwaitingRequest = 0,
AwaitingHeaders = 1,
AwaitingContent = 2,
RequestParsed = 3
};
QString getGuid (void);
QString getGuid (void);
/// @brief Wrapper for sendReplyToClient(), handles m_parsingStatus and signal connect
void sendToClientWithReply (QtHttpReply * reply);
private slots:
void onClientDataReceived (void);
void onClientDataReceived (void);
protected:
ParsingStatus sendReplyToClient (QtHttpReply * reply);
ParsingStatus sendReplyToClient (QtHttpReply * reply);
protected slots:
void onReplySendHeadersRequested (void);
void onReplySendDataRequested (void);
void onReplySendHeadersRequested (void);
void onReplySendDataRequested (void);
private:
QString m_guid;
ParsingStatus m_parsingStatus;
QTcpSocket * m_sockClient;
QtHttpRequest * m_currentRequest;
QtHttpServer * m_serverHandle;
QString m_guid;
ParsingStatus m_parsingStatus;
QTcpSocket * m_sockClient;
QtHttpRequest * m_currentRequest;
QtHttpServer * m_serverHandle;
const bool m_localConnection;
WebSocketClient * m_websocketClient;
WebJsonRpc * m_webJsonRpc;
};

View File

@@ -3,35 +3,36 @@
class QByteArray;
class QtHttpHeader {
class QtHttpHeader
{
public:
static const QByteArray & Server;
static const QByteArray & Date;
static const QByteArray & Host;
static const QByteArray & Accept;
static const QByteArray & ContentType;
static const QByteArray & ContentLength;
static const QByteArray & Connection;
static const QByteArray & Cookie;
static const QByteArray & UserAgent;
static const QByteArray & AcceptCharset;
static const QByteArray & AcceptEncoding;
static const QByteArray & AcceptLanguage;
static const QByteArray & Authorization;
static const QByteArray & CacheControl;
static const QByteArray & ContentMD5;
static const QByteArray & ProxyAuthorization;
static const QByteArray & Range;
static const QByteArray & ContentEncoding;
static const QByteArray & ContentLanguage;
static const QByteArray & ContentLocation;
static const QByteArray & ContentRange;
static const QByteArray & Expires;
static const QByteArray & LastModified;
static const QByteArray & Location;
static const QByteArray & SetCookie;
static const QByteArray & TransferEncoding;
static const QByteArray & ContentDisposition;
static const QByteArray & Server;
static const QByteArray & Date;
static const QByteArray & Host;
static const QByteArray & Accept;
static const QByteArray & ContentType;
static const QByteArray & ContentLength;
static const QByteArray & Connection;
static const QByteArray & Cookie;
static const QByteArray & UserAgent;
static const QByteArray & AcceptCharset;
static const QByteArray & AcceptEncoding;
static const QByteArray & AcceptLanguage;
static const QByteArray & Authorization;
static const QByteArray & CacheControl;
static const QByteArray & ContentMD5;
static const QByteArray & ProxyAuthorization;
static const QByteArray & Range;
static const QByteArray & ContentEncoding;
static const QByteArray & ContentLanguage;
static const QByteArray & ContentLocation;
static const QByteArray & ContentRange;
static const QByteArray & Expires;
static const QByteArray & LastModified;
static const QByteArray & Location;
static const QByteArray & SetCookie;
static const QByteArray & TransferEncoding;
static const QByteArray & ContentDisposition;
static const QByteArray & AccessControlAllow;
// Websocket specific headers
static const QByteArray & Upgrade;

View File

@@ -6,70 +6,35 @@
#include <QDateTime>
QtHttpReply::QtHttpReply (QtHttpServer * parent)
: QObject (parent)
, m_useChunked (false)
, m_statusCode (Ok)
, m_data (QByteArray ())
, m_serverHandle (parent)
: QObject (parent)
, m_useChunked (false)
, m_statusCode (Ok)
, m_data (QByteArray ())
, m_serverHandle (parent)
{
// set some additional headers
addHeader (QtHttpHeader::Date, QDateTime::currentDateTimeUtc ().toString ("ddd, dd MMM yyyy hh:mm:ss t").toUtf8 ());
addHeader (QtHttpHeader::Server, m_serverHandle->getServerName ().toUtf8 ());
// set some additional headers
addHeader (QtHttpHeader::Date, QDateTime::currentDateTimeUtc ().toString ("ddd, dd MMM yyyy hh:mm:ss t").toUtf8 ());
addHeader (QtHttpHeader::Server, m_serverHandle->getServerName ().toUtf8 ());
}
int QtHttpReply::getRawDataSize (void) const {
return m_data.size ();
const QByteArray QtHttpReply::getStatusTextForCode (QtHttpReply::StatusCode statusCode)
{
switch (statusCode)
{
case Ok: return QByteArrayLiteral ("OK.");
case BadRequest: return QByteArrayLiteral ("Bad request !");
case Forbidden: return QByteArrayLiteral ("Forbidden !");
case NotFound: return QByteArrayLiteral ("Not found !");
default: return QByteArrayLiteral ("");
}
}
bool QtHttpReply::useChunked (void) const {
return m_useChunked;
}
void QtHttpReply::addHeader (const QByteArray & header, const QByteArray & value)
{
QByteArray key = header.trimmed ();
QtHttpReply::StatusCode QtHttpReply::getStatusCode (void) const {
return m_statusCode;
}
QByteArray QtHttpReply::getRawData (void) const {
return m_data;
}
QList<QByteArray> QtHttpReply::getHeadersList (void) const {
return m_headersHash.keys ();
}
QByteArray QtHttpReply::getHeader (const QByteArray & header) const {
return m_headersHash.value (header, QByteArray ());
}
const QByteArray QtHttpReply::getStatusTextForCode (QtHttpReply::StatusCode statusCode) {
switch (statusCode) {
case Ok: return QByteArrayLiteral ("OK.");
case BadRequest: return QByteArrayLiteral ("Bad request !");
case Forbidden: return QByteArrayLiteral ("Forbidden !");
case NotFound: return QByteArrayLiteral ("Not found !");
default: return QByteArrayLiteral ("");
}
}
void QtHttpReply::setUseChunked (bool chunked){
m_useChunked = chunked;
}
void QtHttpReply::setStatusCode (QtHttpReply::StatusCode statusCode) {
m_statusCode = statusCode;
}
void QtHttpReply::appendRawData (const QByteArray & data) {
m_data.append (data);
}
void QtHttpReply::addHeader (const QByteArray & header, const QByteArray & value) {
QByteArray key = header.trimmed ();
if (!key.isEmpty ()) {
m_headersHash.insert (key, value);
}
}
void QtHttpReply::resetRawData (void) {
m_data.clear ();
if (!key.isEmpty ())
{
m_headersHash.insert (key, value);
}
}

View File

@@ -8,53 +8,58 @@
class QtHttpServer;
class QtHttpReply : public QObject {
Q_OBJECT
Q_ENUMS (StatusCode)
class QtHttpReply : public QObject
{
Q_OBJECT
Q_ENUMS (StatusCode)
public:
explicit QtHttpReply (QtHttpServer * parent);
explicit QtHttpReply (QtHttpServer * parent);
enum StatusCode {
Ok = 200,
SeeOther = 303,
BadRequest = 400,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
InternalError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
};
enum StatusCode
{
Ok = 200,
SeeOther = 303,
BadRequest = 400,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
InternalError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
};
int getRawDataSize (void) const;
bool useChunked (void) const;
StatusCode getStatusCode (void) const;
QByteArray getRawData (void) const;
QList<QByteArray> getHeadersList (void) const;
int getRawDataSize (void) const { return m_data.size(); };
bool useChunked (void) const { return m_useChunked; };
StatusCode getStatusCode (void) const { return m_statusCode; };
QByteArray getRawData (void) const { return m_data; };
QList<QByteArray> getHeadersList (void) const { return m_headersHash.keys (); };
QByteArray getHeader (const QByteArray & header) const;
QByteArray getHeader (const QByteArray & header) const
{
return m_headersHash.value (header, QByteArray ());
};
static const QByteArray getStatusTextForCode (StatusCode statusCode);
static const QByteArray getStatusTextForCode (StatusCode statusCode);
public slots:
void setUseChunked (bool chunked = false);
void setStatusCode (StatusCode statusCode);
void appendRawData (const QByteArray & data);
void addHeader (const QByteArray & header, const QByteArray & value);
void resetRawData (void);
void setUseChunked (bool chunked = false) { m_useChunked = chunked; };
void setStatusCode (StatusCode statusCode) { m_statusCode = statusCode; };
void appendRawData (const QByteArray & data) { m_data.append(data); };
void addHeader (const QByteArray & header, const QByteArray & value);
void resetRawData (void) { m_data.clear (); };
signals:
void requestSendHeaders (void);
void requestSendData (void);
void requestSendHeaders (void);
void requestSendData (void);
private:
bool m_useChunked;
StatusCode m_statusCode;
QByteArray m_data;
QtHttpServer * m_serverHandle;
QHash<QByteArray, QByteArray> m_headersHash;
bool m_useChunked;
StatusCode m_statusCode;
QByteArray m_data;
QtHttpServer * m_serverHandle;
QHash<QByteArray, QByteArray> m_headersHash;
};
#endif // QTHTTPREPLY_H

View File

@@ -4,80 +4,31 @@
#include "QtHttpServer.h"
QtHttpRequest::QtHttpRequest (QtHttpClientWrapper * client, QtHttpServer * parent)
: QObject (parent)
, m_url (QUrl ())
, m_command (QString ())
, m_data (QByteArray ())
, m_serverHandle (parent)
, m_clientHandle (client)
, m_postData (QtHttpPostData())
: QObject (parent)
, m_url (QUrl ())
, m_command (QString ())
, m_data (QByteArray ())
, m_serverHandle (parent)
, m_clientHandle (client)
, m_postData (QtHttpPostData())
{
// set some additional headers
addHeader (QtHttpHeader::ContentLength, QByteArrayLiteral ("0"));
addHeader (QtHttpHeader::Connection, QByteArrayLiteral ("Keep-Alive"));
// set some additional headers
addHeader (QtHttpHeader::ContentLength, QByteArrayLiteral ("0"));
addHeader (QtHttpHeader::Connection, QByteArrayLiteral ("Keep-Alive"));
}
QUrl QtHttpRequest::getUrl (void) const {
return m_url;
void QtHttpRequest::setClientInfo (const QHostAddress & server, const QHostAddress & client)
{
m_clientInfo.serverAddress = server;
m_clientInfo.clientAddress = client;
}
QString QtHttpRequest::getCommand (void) const {
return m_command;
}
void QtHttpRequest::addHeader (const QByteArray & header, const QByteArray & value)
{
QByteArray key = header.trimmed ();
QtHttpRequest::ClientInfo QtHttpRequest::getClientInfo (void) const {
return m_clientInfo;
}
int QtHttpRequest::getRawDataSize (void) const {
return m_data.size ();
}
QByteArray QtHttpRequest::getRawData (void) const {
return m_data;
}
QtHttpPostData QtHttpRequest::getPostData (void) const {
return m_postData;
}
QList<QByteArray> QtHttpRequest::getHeadersList (void) const {
return m_headersHash.keys ();
}
QtHttpClientWrapper * QtHttpRequest::getClient (void) const {
return m_clientHandle;
}
QByteArray QtHttpRequest::getHeader (const QByteArray & header) const {
return m_headersHash.value (header, QByteArray ());
}
void QtHttpRequest::setUrl (const QUrl & url) {
m_url = url;
}
void QtHttpRequest::setCommand (const QString & command) {
m_command = command;
}
void QtHttpRequest::setClientInfo (const QHostAddress & server, const QHostAddress & client) {
m_clientInfo.serverAddress = server;
m_clientInfo.clientAddress = client;
}
void QtHttpRequest::addHeader (const QByteArray & header, const QByteArray & value) {
QByteArray key = header.trimmed ();
if (!key.isEmpty ()) {
m_headersHash.insert (key, value);
}
}
void QtHttpRequest::appendRawData (const QByteArray & data) {
m_data.append (data);
}
void QtHttpRequest::setPostData (const QtHttpPostData & data) {
m_postData = data;
if (!key.isEmpty ())
{
m_headersHash.insert (key, value);
}
}

View File

@@ -14,45 +14,50 @@ class QtHttpClientWrapper;
using QtHttpPostData = QMap<QString,QByteArray>;
class QtHttpRequest : public QObject {
Q_OBJECT
class QtHttpRequest : public QObject
{
Q_OBJECT
public:
explicit QtHttpRequest (QtHttpClientWrapper * client, QtHttpServer * parent);
explicit QtHttpRequest (QtHttpClientWrapper * client, QtHttpServer * parent);
struct ClientInfo {
QHostAddress serverAddress;
QHostAddress clientAddress;
};
struct ClientInfo
{
QHostAddress serverAddress;
QHostAddress clientAddress;
};
int getRawDataSize (void) const;
QUrl getUrl (void) const;
QString getCommand (void) const;
QByteArray getRawData (void) const;
QList<QByteArray> getHeadersList (void) const;
QtHttpClientWrapper * getClient (void) const;
int getRawDataSize (void) const { return m_data.size (); };
QUrl getUrl (void) const { return m_url; };
QString getCommand (void) const { return m_command; };
QByteArray getRawData (void) const { return m_data; };
QList<QByteArray> getHeadersList (void) const { return m_headersHash.keys (); };
QtHttpClientWrapper * getClient (void) const { return m_clientHandle; };
QtHttpPostData getPostData (void) const { return m_postData; };
ClientInfo getClientInfo (void) const { return m_clientInfo; };
QByteArray getHeader (const QByteArray & header) const;
QtHttpPostData getPostData (void) const;
ClientInfo getClientInfo (void) const;
QByteArray getHeader (const QByteArray & header) const
{
return m_headersHash.value (header, QByteArray ());
};
public slots:
void setUrl (const QUrl & url);
void setCommand (const QString & command);
void setClientInfo (const QHostAddress & server, const QHostAddress & client);
void addHeader (const QByteArray & header, const QByteArray & value);
void appendRawData (const QByteArray & data);
void setPostData (const QtHttpPostData & data);
void setUrl (const QUrl & url) { m_url = url; };
void setCommand (const QString & command) { m_command = command; };
void appendRawData (const QByteArray & data) { m_data.append (data); };
void setPostData (const QtHttpPostData & data) { m_postData = data; };
void setClientInfo (const QHostAddress & server, const QHostAddress & client);
void addHeader (const QByteArray & header, const QByteArray & value);
private:
QUrl m_url;
QString m_command;
QByteArray m_data;
QtHttpServer * m_serverHandle;
QtHttpClientWrapper * m_clientHandle;
QHash<QByteArray, QByteArray> m_headersHash;
ClientInfo m_clientInfo;
QUrl m_url;
QString m_command;
QByteArray m_data;
QtHttpServer * m_serverHandle;
QtHttpClientWrapper * m_clientHandle;
QHash<QByteArray, QByteArray> m_headersHash;
ClientInfo m_clientInfo;
QtHttpPostData m_postData;
};

View File

@@ -1,3 +1,4 @@
#include "QtHttpServer.h"
#include "QtHttpRequest.h"
#include "QtHttpReply.h"
@@ -5,62 +6,51 @@
#include <QUrlQuery>
#include <utils/NetOrigin.h>
const QString & QtHttpServer::HTTP_VERSION = QStringLiteral ("HTTP/1.1");
QtHttpServerWrapper::QtHttpServerWrapper (QObject * parent)
: QTcpServer (parent)
, m_useSsl (false)
, m_useSsl (false)
{
}
QtHttpServerWrapper::~QtHttpServerWrapper (void)
{
}
void QtHttpServerWrapper::setUseSecure (const bool ssl) {
void QtHttpServerWrapper::setUseSecure (const bool ssl)
{
m_useSsl = ssl;
}
void QtHttpServerWrapper::incomingConnection (qintptr handle)
{
QTcpSocket * sock = (m_useSsl
? new QSslSocket (this)
: new QTcpSocket (this));
(sock->setSocketDescriptor (handle))
? addPendingConnection (sock)
: delete sock;
QTcpSocket * sock = (m_useSsl ? new QSslSocket (this) : new QTcpSocket (this));
sock->setSocketDescriptor(handle) ? addPendingConnection (sock) : delete sock;
}
QtHttpServer::QtHttpServer (QObject * parent)
: QObject (parent)
, m_useSsl (false)
: QObject (parent)
, m_useSsl (false)
, m_serverName (QStringLiteral ("The Qt5 HTTP Server"))
, m_netOrigin (NetOrigin::getInstance())
{
m_sockServer = new QtHttpServerWrapper (this);
connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected);
}
const QString & QtHttpServer::getServerName (void) const
{
return m_serverName;
}
quint16 QtHttpServer::getServerPort (void) const
{
return m_sockServer->serverPort ();
}
QString QtHttpServer::getErrorString (void) const
{
return m_sockServer->errorString ();
}
void QtHttpServer::start (quint16 port)
{
if(!m_sockServer->isListening())
(m_sockServer->listen (QHostAddress::Any, port))
? emit started (m_sockServer->serverPort ())
: emit error (m_sockServer->errorString ());
{
m_sockServer->listen (QHostAddress::Any, port)
? emit started (m_sockServer->serverPort ())
: emit error (m_sockServer->errorString ());
}
}
void QtHttpServer::stop (void)
@@ -68,7 +58,6 @@ void QtHttpServer::stop (void)
if (m_sockServer->isListening ())
{
m_sockServer->close ();
// disconnect clients
const QList<QTcpSocket*> socks = m_socksClientsHash.keys();
for(auto sock : socks)
@@ -80,74 +69,49 @@ void QtHttpServer::stop (void)
}
}
void QtHttpServer::setServerName (const QString & serverName)
{
m_serverName = serverName;
}
void QtHttpServer::setUseSecure (const bool ssl)
{
m_useSsl = ssl;
m_sockServer->setUseSecure (m_useSsl);
}
void QtHttpServer::setPrivateKey (const QSslKey & key)
{
m_sslKey = key;
}
void QtHttpServer::setCertificates (const QList<QSslCertificate> & certs)
{
m_sslCerts = certs;
}
void QtHttpServer::onClientConnected (void)
{
while (m_sockServer->hasPendingConnections ())
{
if (QTcpSocket * sock = m_sockServer->nextPendingConnection ())
{
connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
if (m_useSsl)
if(m_netOrigin->accessAllowed(sock->peerAddress(), sock->localAddress()))
{
if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock))
connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
if (m_useSsl)
{
connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
ssl->setLocalCertificateChain (m_sslCerts);
ssl->setPrivateKey (m_sslKey);
ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer);
ssl->startServerEncryption ();
if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock))
{
connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
ssl->setLocalCertificateChain (m_sslCerts);
ssl->setPrivateKey (m_sslKey);
ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer);
ssl->startServerEncryption ();
}
}
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, m_netOrigin->isLocalAddress(sock->peerAddress(), sock->localAddress()), this);
m_socksClientsHash.insert (sock, wrapper);
emit clientConnected (wrapper->getGuid ());
}
else
{
sock->close();
}
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, this);
m_socksClientsHash.insert (sock, wrapper);
emit clientConnected (wrapper->getGuid ());
}
}
}
void QtHttpServer::onClientSslEncrypted (void)
{
}
void QtHttpServer::onClientSslPeerVerifyError (const QSslError & err)
{
Q_UNUSED (err)
}
void QtHttpServer::onClientSslErrors (const QList<QSslError> & errors)
{
Q_UNUSED (errors)
}
void QtHttpServer::onClientSslModeChanged (QSslSocket::SslMode mode)
{
Q_UNUSED (mode)
}
void QtHttpServer::onClientDisconnected (void)
{
if (QTcpSocket * sockClient = qobject_cast<QTcpSocket *> (sender ()))

View File

@@ -13,12 +13,13 @@
class QTcpSocket;
class QTcpServer;
class QtHttpRequest;
class QtHttpReply;
class QtHttpClientWrapper;
class NetOrigin;
class QtHttpServerWrapper : public QTcpServer {
class QtHttpServerWrapper : public QTcpServer
{
Q_OBJECT
public:
@@ -34,7 +35,8 @@ private:
bool m_useSsl;
};
class QtHttpServer : public QObject {
class QtHttpServer : public QObject
{
Q_OBJECT
public:
@@ -44,20 +46,19 @@ public:
typedef void (QSslSocket::* SslErrorSignal) (const QList<QSslError> &);
const QString & getServerName (void) const;
const QString & getServerName (void) const { return m_serverName; };
quint16 getServerPort (void) const;
QString getErrorString (void) const;
bool isListening(void) { return m_sockServer->isListening(); };
quint16 getServerPort (void) const { return m_sockServer->serverPort(); };
QString getErrorString (void) const { return m_sockServer->errorString(); };
bool isListening() { return m_sockServer->isListening(); };
public slots:
void start (quint16 port = 0);
void stop (void);
void setServerName (const QString & serverName);
void setUseSecure (const bool ssl = true);
void setPrivateKey (const QSslKey & key);
void setCertificates (const QList<QSslCertificate> & certs);
void setServerName (const QString & serverName) { m_serverName = serverName; };
void setPrivateKey (const QSslKey & key) { m_sslKey = key; };
void setCertificates (const QList<QSslCertificate> & certs) { m_sslCerts = certs; };
signals:
void started (quint16 port);
@@ -70,19 +71,19 @@ signals:
private slots:
void onClientConnected (void);
void onClientDisconnected (void);
void onClientSslEncrypted (void);
void onClientSslPeerVerifyError (const QSslError & err);
void onClientSslErrors (const QList<QSslError> & errors);
void onClientSslModeChanged (QSslSocket::SslMode mode);
void onClientSslEncrypted (void) { };
void onClientSslPeerVerifyError (const QSslError & err) { Q_UNUSED (err) };
void onClientSslErrors (const QList<QSslError> & errors) { Q_UNUSED (errors) };
void onClientSslModeChanged (QSslSocket::SslMode mode) { Q_UNUSED (mode) };
private:
bool m_useSsl;
QSslKey m_sslKey;
QList<QSslCertificate> m_sslCerts;
QString m_serverName;
NetOrigin* m_netOrigin;
QtHttpServerWrapper * m_sockServer;
QHash<QTcpSocket *, QtHttpClientWrapper *> m_socksClientsHash;
};
#endif // QTHTTPSERVER_H

View File

@@ -6,22 +6,23 @@
#include <api/JsonAPI.h>
WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent)
WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, const bool& localConnection, QtHttpClientWrapper* parent)
: QObject(parent)
, _server(server)
, _wrapper(parent)
, _log(Logger::getInstance("HTTPJSONRPC"))
{
const QString client = request->getClientInfo().clientAddress.toString();
_jsonAPI = new JsonAPI(client, _log, this, true);
_jsonAPI = new JsonAPI(client, _log, localConnection, this, true);
connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebJsonRpc::handleCallback);
}
void WebJsonRpc::handleMessage(QtHttpRequest* request)
{
QByteArray header = request->getHeader("Authorization");
QByteArray data = request->getRawData();
_unlocked = true;
_jsonAPI->handleMessage(data);
_jsonAPI->handleMessage(data,header);
}
void WebJsonRpc::handleCallback(QJsonObject obj)

View File

@@ -1,9 +1,7 @@
#pragma once
// utils includes
#include <utils/Logger.h>
// qt includes
#include <QJsonObject>
class QtHttpServer;
@@ -14,7 +12,7 @@ class JsonAPI;
class WebJsonRpc : public QObject {
Q_OBJECT
public:
WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent);
WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, const bool& localConnection, QtHttpClientWrapper* parent);
void handleMessage(QtHttpRequest* request);

View File

@@ -13,11 +13,12 @@
WebServer::WebServer(const QJsonDocument& config, QObject * parent)
: QObject(parent)
: QObject(parent)
, _config(config)
, _log(Logger::getInstance("WEBSERVER"))
, _server()
{
}
WebServer::~WebServer()
@@ -62,7 +63,8 @@ void WebServer::onServerStarted (quint16 port)
emit stateChange(true);
}
void WebServer::onServerStopped () {
void WebServer::onServerStopped ()
{
Info(_log, "Stopped %s", _server->getServerName().toStdString().c_str());
emit stateChange(false);
}

View File

@@ -1,39 +1,36 @@
#include "webserver/WebSocketClient.h"
#include "WebSocketClient.h"
#include "QtHttpRequest.h"
#include "QtHttpHeader.h"
// hyperion includes
#include <hyperion/Hyperion.h>
// JsonAPI includes
#include <api/JsonAPI.h>
// qt includes
#include <QTcpSocket>
#include <QtEndian>
#include <QCryptographicHash>
#include <QJsonObject>
#include <QHostAddress>
WebSocketClient::WebSocketClient(QByteArray socketKey, QTcpSocket* sock, QObject* parent)
WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, const bool& localConnection, QObject* parent)
: QObject(parent)
, _socket(sock)
, _secWebSocketKey(socketKey)
, _log(Logger::getInstance("WEBSOCKET"))
{
// connect socket; disconnect handled from QtHttpServer
connect(_socket, &QTcpSocket::readyRead , this, &WebSocketClient::handleWebSocketFrame);
const QString client = sock->peerAddress().toString();
// QtHttpRequest contains all headers for handshake
QByteArray secWebSocketKey = request->getHeader(QtHttpHeader::SecWebSocketKey);
const QString client = request->getClientInfo().clientAddress.toString();
// Json processor
_jsonAPI = new JsonAPI(client, _log, this);
_jsonAPI = new JsonAPI(client, _log, localConnection, this);
connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebSocketClient::sendMessage);
Debug(_log, "New connection from %s", QSTRING_CSTR(client));
// do handshake
_secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
QByteArray hash = QCryptographicHash::hash(_secWebSocketKey, QCryptographicHash::Sha1).toBase64();
secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
QByteArray hash = QCryptographicHash::hash(secWebSocketKey, QCryptographicHash::Sha1).toBase64();
QString data
= QString("HTTP/1.1 101 Switching Protocols\r\n")
@@ -47,107 +44,113 @@ WebSocketClient::WebSocketClient(QByteArray socketKey, QTcpSocket* sock, QObject
void WebSocketClient::handleWebSocketFrame(void)
{
// we are on no continious reading from socket from call before
if (!_notEnoughData)
while (_socket->bytesAvailable())
{
getWsFrameHeader(&_wsh);
}
if(_socket->bytesAvailable() < (qint64)_wsh.payloadLength)
{
//printf("not enough data %llu %llu\n", _socket->bytesAvailable(), _wsh.payloadLength);
_notEnoughData=true;
return;
}
_notEnoughData = false;
QByteArray buf = _socket->read(_wsh.payloadLength);
//printf("opcode %x payload bytes %llu avail: %llu\n", _wsh.opCode, _wsh.payloadLength, _socket->bytesAvailable());
if (OPCODE::invalid((OPCODE::value)_wsh.opCode))
{
sendClose(CLOSECODE::INV_TYPE, "invalid opcode");
return;
}
// check the type of data frame
bool isContinuation=false;
switch (_wsh.opCode)
{
case OPCODE::CONTINUATION:
isContinuation = true;
// no break here, just jump over to opcode text
case OPCODE::BINARY:
case OPCODE::TEXT:
// we are on no continious reading from socket from call before
if (!_notEnoughData)
{
// check for protocal violations
if (_onContinuation && !isContinuation)
{
sendClose(CLOSECODE::VIOLATION, "protocol violation, somebody sends frames in between continued frames");
return;
}
if (!_wsh.masked && _wsh.opCode == OPCODE::TEXT)
{
sendClose(CLOSECODE::VIOLATION, "protocol violation, unmasked text frames not allowed");
return;
}
// unmask data
for (int i=0; i < buf.size(); i++)
{
buf[i] = buf[i] ^ _wsh.key[i % 4];
}
_onContinuation = !_wsh.fin || isContinuation;
// frame contains text, extract it, append data if this is a continuation
if (_wsh.fin && ! isContinuation) // one frame
{
_wsReceiveBuffer.clear();
}
_wsReceiveBuffer.append(buf);
// this is the final frame, decode and handle data
if (_wsh.fin)
{
_onContinuation = false;
// if (_wsh.opCode == OPCODE::TEXT)
// {
_jsonAPI->handleMessage(QString(_wsReceiveBuffer));
// }
// else
// {
// handleBinaryMessage(_wsReceiveBuffer);
// }
_wsReceiveBuffer.clear();
}
getWsFrameHeader(&_wsh);
}
break;
case OPCODE::CLOSE:
if(_socket->bytesAvailable() < (qint64)_wsh.payloadLength)
{
//printf("not enough data %llu %llu\n", _socket->bytesAvailable(), _wsh.payloadLength);
_notEnoughData=true;
return;
}
_notEnoughData = false;
QByteArray buf = _socket->read(_wsh.payloadLength);
//printf("opcode %x payload bytes %llu avail: %llu\n", _wsh.opCode, _wsh.payloadLength, _socket->bytesAvailable());
if (OPCODE::invalid((OPCODE::value)_wsh.opCode))
{
sendClose(CLOSECODE::INV_TYPE, "invalid opcode");
return;
}
// check the type of data frame
bool isContinuation=false;
switch (_wsh.opCode)
{
case OPCODE::CONTINUATION:
isContinuation = true;
// no break here, just jump over to opcode text
case OPCODE::BINARY:
case OPCODE::TEXT:
{
sendClose(CLOSECODE::NORMAL);
// check for protocol violations
if (_onContinuation && !isContinuation)
{
sendClose(CLOSECODE::VIOLATION, "protocol violation, somebody sends frames in between continued frames");
return;
}
if (!_wsh.masked && _wsh.opCode == OPCODE::TEXT)
{
sendClose(CLOSECODE::VIOLATION, "protocol violation, unmasked text frames not allowed");
return;
}
// unmask data
for (int i=0; i < buf.size(); i++)
{
buf[i] = buf[i] ^ _wsh.key[i % 4];
}
_onContinuation = !_wsh.fin || isContinuation;
// frame contains text, extract it, append data if this is a continuation
if (_wsh.fin && ! isContinuation) // one frame
{
_wsReceiveBuffer.clear();
}
_wsReceiveBuffer.append(buf);
// this is the final frame, decode and handle data
if (_wsh.fin)
{
_onContinuation = false;
if (_wsh.opCode == OPCODE::TEXT)
{
_jsonAPI->handleMessage(QString(_wsReceiveBuffer));
}
else
{
handleBinaryMessage(_wsReceiveBuffer);
}
_wsReceiveBuffer.clear();
}
}
break;
case OPCODE::PING:
{
// ping received, send pong
quint8 pong[] = {OPCODE::PONG, 0};
_socket->write((const char*)pong, 2);
_socket->flush();
}
break;
case OPCODE::CLOSE:
{
sendClose(CLOSECODE::NORMAL);
}
break;
case OPCODE::PONG:
{
Error(_log, "pong received, protocol violation!");
}
case OPCODE::PING:
{
// ping received, send pong
quint8 pong[] = {OPCODE::PONG, 0};
_socket->write((const char*)pong, 2);
_socket->flush();
}
break;
default:
Warning(_log, "strange %d\n%s\n", _wsh.opCode, QSTRING_CSTR(QString(buf)));
case OPCODE::PONG:
{
Error(_log, "pong received, protocol violation!");
}
default:
Warning(_log, "strange %d\n%s\n", _wsh.opCode, QSTRING_CSTR(QString(buf)));
}
}
}
@@ -223,7 +226,7 @@ void WebSocketClient::sendClose(int status, QString reason)
_socket->close();
}
/*
void WebSocketClient::handleBinaryMessage(QByteArray &data)
{
//uint8_t priority = data.at(0);
@@ -242,10 +245,10 @@ void WebSocketClient::handleBinaryMessage(QByteArray &data)
image.resize(width, height);
memcpy(image.memptr(), data.data()+4, imgSize);
_hyperion->registerInput();
_hyperion->setInputImage(priority, image, duration_s*1000);
//_hyperion->registerInput();
//_hyperion->setInputImage(priority, image, duration_s*1000);
}
*/
qint64 WebSocketClient::sendMessage(QJsonObject obj)
{

View File

@@ -0,0 +1,72 @@
#pragma once
#include <utils/Logger.h>
#include "WebSocketUtils.h"
class QTcpSocket;
class QtHttpRequest;
class Hyperion;
class JsonAPI;
class WebSocketClient : public QObject {
Q_OBJECT
public:
WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, const bool& localConnection, QObject* parent);
struct WebSocketHeader
{
bool fin;
quint8 opCode;
bool masked;
quint64 payloadLength;
char key[4];
};
private:
QTcpSocket* _socket;
Logger* _log;
Hyperion* _hyperion;
JsonAPI* _jsonAPI;
void getWsFrameHeader(WebSocketHeader* header);
void sendClose(int status, QString reason = "");
void handleBinaryMessage(QByteArray &data);
qint64 sendMessage_Raw(const char* data, quint64 size);
qint64 sendMessage_Raw(QByteArray &data);
QByteArray makeFrameHeader(quint8 opCode, quint64 payloadLength, bool lastFrame);
/// The buffer used for reading data from the socket
QByteArray _receiveBuffer;
/// buffer for websockets multi frame receive
QByteArray _wsReceiveBuffer;
quint8 _maskKey[4];
bool _onContinuation = false;
// true when data is missing for parsing
bool _notEnoughData = false;
// websocket header store
WebSocketHeader _wsh;
// masks for fields in the basic header
static uint8_t const BHB0_OPCODE = 0x0F;
static uint8_t const BHB0_RSV3 = 0x10;
static uint8_t const BHB0_RSV2 = 0x20;
static uint8_t const BHB0_RSV1 = 0x40;
static uint8_t const BHB0_FIN = 0x80;
static uint8_t const BHB1_PAYLOAD = 0x7F;
static uint8_t const BHB1_MASK = 0x80;
static uint8_t const payload_size_code_16bit = 0x7E; // 126
static uint8_t const payload_size_code_64bit = 0x7F; // 127
static const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message
private slots:
void handleWebSocketFrame(void);
qint64 sendMessage(QJsonObject obj);
};

View File

@@ -0,0 +1,75 @@
#pragma once
/// Constants and utility functions related to WebSocket opcodes
/**
* WebSocket Opcodes are 4 bits. See RFC6455 section 5.2.
*/
namespace OPCODE
{
enum value
{
CONTINUATION = 0x0,
TEXT = 0x1,
BINARY = 0x2,
RSV3 = 0x3,
RSV4 = 0x4,
RSV5 = 0x5,
RSV6 = 0x6,
RSV7 = 0x7,
CLOSE = 0x8,
PING = 0x9,
PONG = 0xA,
CONTROL_RSVB = 0xB,
CONTROL_RSVC = 0xC,
CONTROL_RSVD = 0xD,
CONTROL_RSVE = 0xE,
CONTROL_RSVF = 0xF
};
/// Check if an opcode is reserved
/**
* @param v The opcode to test.
* @return Whether or not the opcode is reserved.
*/
inline bool reserved(value v)
{
return (v >= RSV3 && v <= RSV7) || (v >= CONTROL_RSVB && v <= CONTROL_RSVF);
}
/// Check if an opcode is invalid
/**
* Invalid opcodes are negative or require greater than 4 bits to store.
*
* @param v The opcode to test.
* @return Whether or not the opcode is invalid.
*/
inline bool invalid(value v)
{
return (v > 0xF || v < 0);
}
/// Check if an opcode is for a control frame
/**
* @param v The opcode to test.
* @return Whether or not the opcode is a control opcode.
*/
inline bool is_control(value v)
{
return v >= 0x8;
}
}
namespace CLOSECODE
{
enum value
{
NORMAL = 1000,
AWAY = 1001,
TERM = 1002,
INV_TYPE = 1003,
INV_DATA = 1007,
VIOLATION = 1008,
BIG_MSG = 1009,
UNEXPECTED= 1011
};
}