mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
Merge pull request #578 from Paulchen-Panther/api_auth
Token Management, Database, ...
This commit is contained in:
@@ -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)
|
||||
|
44
libsrc/api/JSONRPC_schema/schema-authorize.json
Normal file
44
libsrc/api/JSONRPC_schema/schema-authorize.json
Normal 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
|
||||
}
|
@@ -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":
|
||||
|
29
libsrc/api/JSONRPC_schema/schema-instance.json
Normal file
29
libsrc/api/JSONRPC_schema/schema-instance.json
Normal 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
|
||||
}
|
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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));
|
||||
}
|
||||
|
@@ -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];
|
||||
|
@@ -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
|
||||
|
@@ -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());
|
||||
|
@@ -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
18
libsrc/db/CMakeLists.txt
Normal 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
396
libsrc/db/DBManager.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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()));
|
||||
|
@@ -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());
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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()
|
||||
|
169
libsrc/hyperion/AuthManager.cpp
Normal file
169
libsrc/hyperion/AuthManager.cpp
Normal 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();
|
||||
}
|
@@ -25,5 +25,6 @@ target_link_libraries(hyperion
|
||||
bonjour
|
||||
boblightserver
|
||||
effectengine
|
||||
database
|
||||
${QT_LIBRARIES}
|
||||
)
|
||||
|
@@ -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);
|
||||
|
@@ -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));
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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()
|
||||
|
203
libsrc/hyperion/HyperionIManager.cpp
Normal file
203
libsrc/hyperion/HyperionIManager.cpp
Normal 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();
|
||||
}
|
@@ -180,8 +180,6 @@ void LinearColorSmoothing::componentStateChange(const hyperion::Components compo
|
||||
|
||||
void LinearColorSmoothing::setEnable(bool enable)
|
||||
{
|
||||
LedDevice::setEnable(enable);
|
||||
|
||||
if (!enable)
|
||||
{
|
||||
_timer->stop();
|
||||
|
@@ -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)
|
||||
///
|
||||
|
@@ -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();)
|
||||
{
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -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>
|
||||
|
59
libsrc/hyperion/schema/schema-network.json
Normal file
59
libsrc/hyperion/schema/schema-network.json
Normal 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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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");
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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}
|
||||
)
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -1 +0,0 @@
|
||||
|
@@ -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();
|
||||
|
76
libsrc/utils/NetOrigin.cpp
Normal file
76
libsrc/utils/NetOrigin.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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() );
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
|
@@ -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 ()))
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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)
|
||||
{
|
||||
|
72
libsrc/webserver/WebSocketClient.h
Normal file
72
libsrc/webserver/WebSocketClient.h
Normal 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);
|
||||
};
|
75
libsrc/webserver/WebSocketUtils.h
Normal file
75
libsrc/webserver/WebSocketUtils.h
Normal 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
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user