From aa465c018c45b32c9fb67a28b09b1aff1bc6d280 Mon Sep 17 00:00:00 2001 From: Paulchen Panther <16664240+Paulchen-Panther@users.noreply.github.com> Date: Sun, 18 Oct 2020 17:05:07 +0200 Subject: [PATCH 1/9] Fix issues #1022, #1019, #997, #993, #992, #976, #969, #964, #980 (#1036) --- .gitignore | 3 ++ include/api/API.h | 31 ++++++------ include/api/JsonAPI.h | 7 +-- include/hyperion/AuthManager.h | 10 ++-- include/hyperion/HyperionIManager.h | 21 ++++++-- include/ssdp/SSDPHandler.h | 2 +- include/ssdp/SSDPServer.h | 42 ++++++++++++---- libsrc/api/API.cpp | 50 ++++++++++++-------- libsrc/api/JsonAPI.cpp | 28 ++++++----- libsrc/effectengine/EffectEngine.cpp | 6 +-- libsrc/hyperion/AuthManager.cpp | 12 ++--- libsrc/hyperion/HyperionIManager.cpp | 15 +++++- libsrc/hyperion/PriorityMuxer.cpp | 14 ++++-- libsrc/leddevice/dev_net/ProviderRestApi.cpp | 5 +- libsrc/ssdp/SSDPDescription.h | 6 +++ libsrc/ssdp/SSDPHandler.cpp | 36 +++++++++++++- libsrc/webserver/QtHttpClientWrapper.cpp | 2 +- src/hyperiond/hyperiond.cpp | 7 ++- 18 files changed, 207 insertions(+), 90 deletions(-) diff --git a/.gitignore b/.gitignore index fd2b546b..f92845f5 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ compile_commands.json # Autogenerated by flatbuffers libsrc/flatbufserver/hyperion_reply_generated.h libsrc/flatbufserver/hyperion_request_generated.h + +# Kdevelop project files +*.kdev* diff --git a/include/api/API.h b/include/api/API.h index dd92b2a9..aa134ccc 100644 --- a/include/api/API.h +++ b/include/api/API.h @@ -112,8 +112,9 @@ protected: /// @param dat The effect data /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF /// REQUIRED dat fields: effectName, priority, duration, origin + /// @return True on success else false /// - void setEffect(const EffectCmdData &dat, hyperion::Components callerComp = hyperion::COMP_INVALID); + bool setEffect(const EffectCmdData &dat, hyperion::Components callerComp = hyperion::COMP_INVALID); /// /// @brief Set source auto select enabled or disabled @@ -174,8 +175,10 @@ protected: /// /// @brief Start instance /// @param index The instance index + /// @param tan The tan + /// @return True on success else false /// - void startInstance(quint8 index); + bool startInstance(quint8 index, int tan = 0); /// /// @brief Stop instance @@ -277,8 +280,9 @@ protected: /// @brief Set a new token request /// @param comment The comment /// @param id The id + /// @param tan The tan /// - void setNewTokenRequest(const QString &comment, const QString &id); + void setNewTokenRequest(const QString &comment, const QString &id, const int &tan); /// /// @brief Cancel new token request @@ -367,7 +371,7 @@ signals: /// /// @brief Emits whenever a new Token request is pending. This signal is just active when ADMIN ACCESS has been granted - /// @param id The id of the request + /// @param id The id of the request /// @param comment The comment of the request; If the commen is EMPTY the request has been revoked by the caller. So remove it from the pending list /// void onPendingTokenRequest(const QString &id, const QString &comment); @@ -378,8 +382,15 @@ signals: /// @param token The new token that is now valid /// @param comment The comment that was part of the request /// @param id The id that was part of the request + /// @param tan The tan that was part of the request /// - void onTokenResponse(bool success, const QString &token, const QString &comment, const QString &id); + void onTokenResponse(bool success, const QString &token, const QString &comment, const QString &id, const int &tan); + + /// + /// @brief Handle emits from HyperionIManager of startInstance request, just if QObject matches with this instance it will emit. + /// @param tan The tan that was part of the request + /// + void onStartInstanceResponse(const int &tan); private slots: /// @@ -388,16 +399,6 @@ private slots: /// void requestActiveRegister(QObject *callerInstance); - /// - /// @brief See onTokenResponse(). Here we validate the caller instance and on success we will emit onTokenResponse() - /// @param success If true the request was accepted else false and no token was created - /// @param caller The origin caller instance who requested this token - /// @param token The new token that is now valid - /// @param comment The comment that was part of the request - /// @param id The id that was part of the request - /// - void checkTokenResponse(bool success, QObject *caller, const QString &token, const QString &comment, const QString &id); - private: void stopDataConnectionss(); diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h index 0601415d..d7009a27 100644 --- a/include/api/JsonAPI.h +++ b/include/api/JsonAPI.h @@ -65,8 +65,8 @@ public slots: private slots: /// /// @brief Handle emits from API of a new Token request. - /// @param id The id of the request - /// @param The comment which needs to be accepted + /// @param id The id of the request + /// @param comment The comment which needs to be accepted /// void newPendingTokenRequest(const QString &id, const QString &comment); @@ -76,8 +76,9 @@ private slots: /// @param token The new token that is now valid /// @param comment The comment that was part of the request /// @param id The id that was part of the request + /// @param tan The tan that was part of the request /// - void handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &id); + void handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &id, const int &tan); /// /// @brief Handle whenever the state of a instance (HyperionIManager) changes according to enum instanceState diff --git a/include/hyperion/AuthManager.h b/include/hyperion/AuthManager.h index 41906465..1f22fb3a 100644 --- a/include/hyperion/AuthManager.h +++ b/include/hyperion/AuthManager.h @@ -29,6 +29,7 @@ public: QString id; QString comment; QObject *caller; + int tan; uint64_t timeoutTime; QString token; QString lastUse; @@ -142,16 +143,16 @@ public slots: /// @param caller The QObject of the caller to deliver the reply /// @param comment The comment as ident helper /// @param id The id created by the caller + /// @param tan The tan created by the caller /// - void setNewTokenRequest(QObject *caller, const QString &comment, const QString &id); + void setNewTokenRequest(QObject *caller, const QString &comment, const QString &id, const int &tan = 0); /// /// @brief Cancel a pending token request with the provided comment and id as identifier helper /// @param caller The QObject of the caller to deliver the reply - /// @param comment The comment as ident helper /// @param id The id created by the caller /// - void cancelNewTokenRequest(QObject *caller, const QString &comment, const QString &id); + void cancelNewTokenRequest(QObject *caller, const QString &, const QString &id); /// /// @brief Handle a token request by id, generate token and inform token caller or deny @@ -200,8 +201,9 @@ signals: /// @param token The new token that is now valid /// @param comment The comment that was part of the request /// @param id The id that was part of the request + /// @param tan The tan that was part of the request /// - void tokenResponse(bool success, QObject *caller, const QString &token, const QString &comment, const QString &id); + void tokenResponse(bool success, QObject *caller, const QString &token, const QString &comment, const QString &id, const int &tan); /// /// @brief Emits whenever the token list changes diff --git a/include/hyperion/HyperionIManager.h b/include/hyperion/HyperionIManager.h index 73b86654..c7b1fca9 100644 --- a/include/hyperion/HyperionIManager.h +++ b/include/hyperion/HyperionIManager.h @@ -28,6 +28,12 @@ class HyperionIManager : public QObject Q_OBJECT public: + struct PendingRequests + { + QObject *caller; + int tan; + }; + // global instance pointer static HyperionIManager* getInstance() { return HIMinstance; } static HyperionIManager* HIMinstance; @@ -54,11 +60,11 @@ public slots: /// /// @brief Start a Hyperion instance - /// @param instance Instance index - /// @param block If true return when thread has been started + /// @param instance Instance index + /// @param block If true return when thread has been started /// @return Return true on success, false if not found in db /// - bool startInstance(quint8 inst, bool block = false); + bool startInstance(quint8 inst, bool block = false, QObject *caller = nullptr, int tan = 0); /// /// @brief Stop a Hyperion instance @@ -110,6 +116,13 @@ signals: /// void change(); + /// + /// @brief Emits when the user has requested to start a instance + /// @param caller The origin caller instance who requested + /// @param tan The tan that was part of the request + /// + void startInstanceResponse(QObject *caller, const int &tan); + signals: /////////////////////////////////////// /// FROM HYPERIONDAEMON TO HYPERION /// @@ -180,4 +193,6 @@ private: const QString _rootPath; QMap _runningInstances; QList _startQueue; + /// All pending requests + QMap _pendingRequests; }; diff --git a/include/ssdp/SSDPHandler.h b/include/ssdp/SSDPHandler.h index dc20bd29..9f14cace 100644 --- a/include/ssdp/SSDPHandler.h +++ b/include/ssdp/SSDPHandler.h @@ -20,7 +20,7 @@ class SSDPHandler : public SSDPServer { Q_OBJECT public: - SSDPHandler(WebServer* webserver, quint16 flatBufPort, quint16 jsonServerPort, const QString &name, QObject * parent = nullptr); + SSDPHandler(WebServer* webserver, quint16 flatBufPort, quint16 protoBufPort, quint16 jsonServerPort, quint16 sslPort, const QString &name, QObject * parent = nullptr); ~SSDPHandler() override; /// diff --git a/include/ssdp/SSDPServer.h b/include/ssdp/SSDPServer.h index 9cff55d4..6bfe5d1c 100644 --- a/include/ssdp/SSDPServer.h +++ b/include/ssdp/SSDPServer.h @@ -85,16 +85,36 @@ public: quint16 getFlatBufPort() const { return _fbsPort.toInt(); }; /// - /// @brief set new jsonserver server port + /// @brief set new protobuf server port + /// + void setProtoBufPort(quint16 port) { _pbsPort = QString::number(port); }; + + /// + /// @brief Get current protobuf server port + /// + quint16 getProtoBufPort() const { return _pbsPort.toInt(); }; + + /// + /// @brief set new json server port /// void setJsonServerPort(quint16 port) { _jssPort = QString::number(port); }; /// - /// @brief get new jsonserver server port + /// @brief get new json server port /// quint16 getJsonServerPort() const { return _jssPort.toInt(); }; - /// + /// + /// @brief set new ssl server port + /// + void setSSLServerPort(quint16 port) { _sslPort = QString::number(port); }; + + /// + /// @brief get new ssl server port + /// + quint16 getSSLServerPort() const { return _sslPort.toInt(); }; + + /// /// @brief set new hyperion name /// void setHyperionName(const QString &name) { _name = name; }; @@ -119,13 +139,15 @@ private: Logger* _log; QUdpSocket* _udpSocket; - QString _serverHeader; - QString _uuid; - QString _fbsPort; - QString _jssPort; - QString _name; - QString _descAddress; - bool _running; + QString _serverHeader, + _uuid, + _fbsPort, + _pbsPort, + _jssPort, + _sslPort, + _name, + _descAddress; + bool _running; private slots: void readPendingDatagrams(); diff --git a/libsrc/api/API.cpp b/libsrc/api/API.cpp index 298ad334..7a22d9cc 100644 --- a/libsrc/api/API.cpp +++ b/libsrc/api/API.cpp @@ -56,7 +56,18 @@ API::API(Logger *log, bool localConnection, QObject *parent) //connect(ApiSync::getInstance(), &ApiSync::requestActiveRegister, this, &API::requestActiveRegister, Qt::QueuedConnection); // connect to possible token responses that has been requested - connect(_authManager, &AuthManager::tokenResponse, this, &API::checkTokenResponse); + connect(_authManager, &AuthManager::tokenResponse, [=] (bool success, QObject *caller, const QString &token, const QString &comment, const QString &id, const int &tan) + { + if (this == caller) + emit onTokenResponse(success, token, comment, id, tan); + }); + + // connect to possible startInstance responses that has been requested + connect(_instanceManager, &HyperionIManager::startInstanceResponse, [=] (QObject *caller, const int &tan) + { + if (this == caller) + emit onStartInstanceResponse(tan); + }); } void API::init() @@ -211,16 +222,19 @@ void API::setVideoMode(VideoMode mode, hyperion::Components callerComp) QMetaObject::invokeMethod(_hyperion, "setVideoMode", Qt::QueuedConnection, Q_ARG(VideoMode, mode)); } -void API::setEffect(const EffectCmdData &dat, hyperion::Components callerComp) +bool API::setEffect(const EffectCmdData &dat, hyperion::Components callerComp) { + int res; if (!dat.args.isEmpty()) { - QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::QueuedConnection, Q_ARG(QString, dat.effectName), Q_ARG(QJsonObject, dat.args), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.pythonScript), Q_ARG(QString, dat.origin), Q_ARG(QString, dat.data)); + QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, res), Q_ARG(QString, dat.effectName), Q_ARG(QJsonObject, dat.args), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.pythonScript), Q_ARG(QString, dat.origin), Q_ARG(QString, dat.data)); } else { - QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::QueuedConnection, Q_ARG(QString, dat.effectName), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.origin)); + QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, res), Q_ARG(QString, dat.effectName), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.origin)); } + + return res >= 0; } void API::setSourceAutoSelect(bool state, hyperion::Components callerComp) @@ -285,9 +299,14 @@ QVector API::getAllInstanceData() return vec; } -void API::startInstance(quint8 index) +bool API::startInstance(quint8 index, int tan) { - QMetaObject::invokeMethod(_instanceManager, "startInstance", Qt::QueuedConnection, Q_ARG(quint8, index)); + bool res; + (_instanceManager->thread() != this->thread()) + ? QMetaObject::invokeMethod(_instanceManager, "startInstance", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(quint8, index), Q_ARG(bool, false), Q_ARG(QObject*, this), Q_ARG(int, tan)) + : res = _instanceManager->startInstance(index, false, this, tan); + + return res; } void API::stopInstance(quint8 index) @@ -407,9 +426,9 @@ QString API::deleteToken(const QString &id) return ""; } -void API::setNewTokenRequest(const QString &comment, const QString &id) +void API::setNewTokenRequest(const QString &comment, const QString &id, const int &tan) { - QMetaObject::invokeMethod(_authManager, "setNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, id)); + QMetaObject::invokeMethod(_authManager, "setNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, id), Q_ARG(int, tan)); } void API::cancelNewTokenRequest(const QString &comment, const QString &id) @@ -465,12 +484,11 @@ bool API::getUserToken(QString &userToken) bool API::isTokenAuthorized(const QString &token) { - bool res; - QMetaObject::invokeMethod(_authManager, "isTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, token)); - if (res) - _authorized = true; + (_authManager->thread() != this->thread()) + ? QMetaObject::invokeMethod(_authManager, "isTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _authorized), Q_ARG(QString, token)) + : _authorized = _authManager->isTokenAuthorized(token); - return res; + return _authorized; } bool API::isUserAuthorized(const QString &password) @@ -503,12 +521,6 @@ void API::logout() stopDataConnectionss(); } -void API::checkTokenResponse(bool success, QObject *caller, const QString &token, const QString &comment, const QString &id) -{ - if (this == caller) - emit onTokenResponse(success, token, comment, id); -} - void API::stopDataConnectionss() { } diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index a3b0d031..0bedbeac 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -243,9 +243,10 @@ void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &com dat.data = message["imageData"].toString("").toUtf8(); dat.args = message["effect"].toObject()["args"].toObject(); - API::setEffect(dat); - - sendSuccessReply(command, tan); + if (API::setEffect(dat)) + sendSuccessReply(command, tan); + else + sendErrorReply("Effect '" + dat.effectName + "' not found", command, tan); } void JsonAPI::handleCreateEffectCommand(const QJsonObject &message, const QString &command, int tan) @@ -351,8 +352,10 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString item["value"] = LEDcolor; } - // priorities[priorities.size()] = item; - priorities.append(item); + + (priority == currentPriority) + ? priorities.prepend(item) + : priorities.append(item); } info["priorities"] = priorities; @@ -1186,7 +1189,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString & const QString &comment = message["comment"].toString().trimmed(); const bool &acc = message["accept"].toBool(true); if (acc) - API::setNewTokenRequest(comment, id); + API::setNewTokenRequest(comment, id, tan); else API::cancelNewTokenRequest(comment, id); // client should wait for answer @@ -1323,9 +1326,10 @@ void JsonAPI::handleInstanceCommand(const QJsonObject &message, const QString &c if (subc == "startInstance") { - // silent fail - API::startInstance(inst); - sendSuccessReply(command + "-" + subc, tan); + connect(this, &API::onStartInstanceResponse, [=] (const int &tan) { sendSuccessReply(command + "-" + subc, tan); }); + if (!API::startInstance(inst, tan)) + sendErrorReply("Can't start Hyperion instance index " + QString::number(inst), command + "-" + subc, tan); + return; } @@ -1557,7 +1561,7 @@ void JsonAPI::newPendingTokenRequest(const QString &id, const QString &comment) sendSuccessDataReply(QJsonDocument(obj), "authorize-tokenRequest", 1); } -void JsonAPI::handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &id) +void JsonAPI::handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &id, const int &tan) { const QString cmd = "authorize-requestToken"; QJsonObject result; @@ -1566,9 +1570,9 @@ void JsonAPI::handleTokenResponse(bool success, const QString &token, const QStr result["id"] = id; if (success) - sendSuccessDataReply(QJsonDocument(result), cmd); + sendSuccessDataReply(QJsonDocument(result), cmd, tan); else - sendErrorReply("Token request timeout or denied", cmd, 5); + sendErrorReply("Token request timeout or denied", cmd, tan); } void JsonAPI::handleInstanceStateChange(InstanceState state, quint8 instance, const QString &name) diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp index e4a4bb06..14169890 100644 --- a/libsrc/effectengine/EffectEngine.cpp +++ b/libsrc/effectengine/EffectEngine.cpp @@ -141,8 +141,6 @@ int EffectEngine::runEffect(const QString &effectName, int priority, int timeout int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args, int priority, int timeout, const QString &pythonScript, const QString &origin, unsigned smoothCfg, const QString &imageData) { - Info( _log, "Run effect \"%s\" on channel %d", QSTRING_CSTR(effectName), priority); - if (pythonScript.isEmpty()) { const EffectDefinition *effectDefinition = nullptr; @@ -157,12 +155,14 @@ int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args, if (effectDefinition == nullptr) { // no such effect - Error(_log, "Effect %s not found", QSTRING_CSTR(effectName)); + Error(_log, "Effect \"%s\" not found", QSTRING_CSTR(effectName)); return -1; } + Info( _log, "Run effect \"%s\" on channel %d", QSTRING_CSTR(effectName), priority); return runEffectScript(effectDefinition->script, effectName, (args.isEmpty() ? effectDefinition->args : args), priority, timeout, origin, effectDefinition->smoothCfg); } + Info( _log, "Run effect \"%s\" on channel %d", QSTRING_CSTR(effectName), priority); return runEffectScript(pythonScript, effectName, args, priority, timeout, origin, smoothCfg, imageData); } diff --git a/libsrc/hyperion/AuthManager.cpp b/libsrc/hyperion/AuthManager.cpp index 996291d0..17f76706 100644 --- a/libsrc/hyperion/AuthManager.cpp +++ b/libsrc/hyperion/AuthManager.cpp @@ -150,18 +150,18 @@ bool AuthManager::resetHyperionUser() return _authTable->resetHyperionUser(); } -void AuthManager::setNewTokenRequest(QObject *caller, const QString &comment, const QString &id) +void AuthManager::setNewTokenRequest(QObject *caller, const QString &comment, const QString &id, const int &tan) { if (!_pendingRequests.contains(id)) { - AuthDefinition newDef{id, comment, caller, uint64_t(QDateTime::currentMSecsSinceEpoch() + 180000)}; + AuthDefinition newDef{id, comment, caller, tan, uint64_t(QDateTime::currentMSecsSinceEpoch() + 180000)}; _pendingRequests[id] = newDef; _timer->start(); emit newPendingTokenRequest(id, comment); } } -void AuthManager::cancelNewTokenRequest(QObject *caller, const QString &comment, const QString &id) +void AuthManager::cancelNewTokenRequest(QObject *caller, const QString &, const QString &id) { if (_pendingRequests.contains(id)) { @@ -182,12 +182,12 @@ void AuthManager::handlePendingTokenRequest(const QString &id, bool accept) { const QString token = QUuid::createUuid().toString().remove("{").remove("}"); _authTable->createToken(token, def.comment, id); - emit tokenResponse(true, def.caller, token, def.comment, id); + emit tokenResponse(true, def.caller, token, def.comment, id, def.tan); emit tokenChange(getTokenList()); } else { - emit tokenResponse(false, def.caller, QString(), def.comment, id); + emit tokenResponse(false, def.caller, QString(), def.comment, id, def.tan); } } } @@ -249,7 +249,7 @@ void AuthManager::checkTimeout() const AuthDefinition &def = i.value(); if (def.timeoutTime <= now) { - emit tokenResponse(false, def.caller, QString(), def.comment, def.id); + emit tokenResponse(false, def.caller, QString(), def.comment, def.id, def.tan); _pendingRequests.remove(i.key()); } } diff --git a/libsrc/hyperion/HyperionIManager.cpp b/libsrc/hyperion/HyperionIManager.cpp index 255055e8..9613af25 100644 --- a/libsrc/hyperion/HyperionIManager.cpp +++ b/libsrc/hyperion/HyperionIManager.cpp @@ -67,7 +67,7 @@ void HyperionIManager::toggleStateAllInstances(bool pause) } } -bool HyperionIManager::startInstance(quint8 inst, bool block) +bool HyperionIManager::startInstance(quint8 inst, bool block, QObject* caller, int tan) { if(_instanceTable->instanceExist(inst)) { @@ -104,6 +104,12 @@ bool HyperionIManager::startInstance(quint8 inst, bool block) while(!hyperionThread->isRunning()){}; } + if (!_pendingRequests.contains(inst) && caller != nullptr) + { + PendingRequests newDef{caller, tan}; + _pendingRequests[inst] = newDef; + } + 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))); @@ -211,4 +217,11 @@ void HyperionIManager::handleStarted() _runningInstances.insert(instance, hyperion); emit instanceStateChanged(InstanceState::H_STARTED, instance); emit change(); + + if (_pendingRequests.contains(instance)) + { + PendingRequests def = _pendingRequests.take(instance); + emit startInstanceResponse(def.caller, def.tan); + _pendingRequests.remove(instance); + } } diff --git a/libsrc/hyperion/PriorityMuxer.cpp b/libsrc/hyperion/PriorityMuxer.cpp index b7687e25..17cfe319 100644 --- a/libsrc/hyperion/PriorityMuxer.cpp +++ b/libsrc/hyperion/PriorityMuxer.cpp @@ -142,9 +142,11 @@ hyperion::Components PriorityMuxer::getComponentOfPriority(int priority) const void PriorityMuxer::registerInput(int priority, hyperion::Components component, const QString& origin, const QString& owner, unsigned smooth_cfg) { // detect new registers - bool newInput = false; - if(!_activeInputs.contains(priority)) + bool newInput, reusedInput = false; + if (!_activeInputs.contains(priority)) newInput = true; + else + reusedInput = true; InputInfo& input = _activeInputs[priority]; input.priority = priority; @@ -154,12 +156,15 @@ void PriorityMuxer::registerInput(int priority, hyperion::Components component, input.smooth_cfg = smooth_cfg; input.owner = owner; - if(newInput) + if (newInput) { Debug(_log,"Register new input '%s/%s' with priority %d as inactive", QSTRING_CSTR(origin), hyperion::componentToIdString(component), priority); - emit prioritiesChanged(); + if (!_sourceAutoSelectEnabled) // emit 'prioritiesChanged' only on when _sourceAutoSelectEnabled is false + emit prioritiesChanged(); return; } + + if (reusedInput) emit prioritiesChanged(); } bool PriorityMuxer::setInput(int priority, const std::vector& ledColors, int64_t timeout_ms) @@ -339,7 +344,6 @@ void PriorityMuxer::setCurrentTime() _prevVisComp = comp; emit visibleComponentChanged(comp); } - emit prioritiesChanged(); } } diff --git a/libsrc/leddevice/dev_net/ProviderRestApi.cpp b/libsrc/leddevice/dev_net/ProviderRestApi.cpp index 5ad71e90..29ba212d 100644 --- a/libsrc/leddevice/dev_net/ProviderRestApi.cpp +++ b/libsrc/leddevice/dev_net/ProviderRestApi.cpp @@ -39,10 +39,7 @@ ProviderRestApi::ProviderRestApi() ProviderRestApi::~ProviderRestApi() { - if ( _networkManager != nullptr ) - { - delete _networkManager; - } + delete _networkManager; } void ProviderRestApi::setBasePath(const QString &basePath) diff --git a/libsrc/ssdp/SSDPDescription.h b/libsrc/ssdp/SSDPDescription.h index 70b37da6..4bf3f76f 100644 --- a/libsrc/ssdp/SSDPDescription.h +++ b/libsrc/ssdp/SSDPDescription.h @@ -27,6 +27,12 @@ static const QString SSDP_DESCRIPTION = "" "https://www.hyperion-project.org" "%4" "uuid:%4" + "" + "%5" + "%6" + "%7" + "%8" + "" "index.html" "" "" diff --git a/libsrc/ssdp/SSDPHandler.cpp b/libsrc/ssdp/SSDPHandler.cpp index 48189fb1..62397cb3 100644 --- a/libsrc/ssdp/SSDPHandler.cpp +++ b/libsrc/ssdp/SSDPHandler.cpp @@ -15,14 +15,16 @@ static const QString SSDP_HYPERION_ST("urn:hyperion-project.org:device:basic:1"); -SSDPHandler::SSDPHandler(WebServer* webserver, quint16 flatBufPort, quint16 jsonServerPort, const QString& name, QObject * parent) +SSDPHandler::SSDPHandler(WebServer* webserver, quint16 flatBufPort, quint16 protoBufPort, quint16 jsonServerPort, quint16 sslPort, const QString& name, QObject * parent) : SSDPServer(parent) , _webserver(webserver) , _localAddress() , _NCA(nullptr) { setFlatBufPort(flatBufPort); + setProtoBufPort(protoBufPort); setJsonServerPort(jsonServerPort); + setSSLServerPort(sslPort); setHyperionName(name); } @@ -85,6 +87,14 @@ void SSDPHandler::handleSettingsUpdate(settings::type type, const QJsonDocument& } } + if(type == settings::PROTOSERVER) + { + if(obj["port"].toInt() != SSDPServer::getProtoBufPort()) + { + SSDPServer::setProtoBufPort(obj["port"].toInt()); + } + } + if(type == settings::JSONSERVER) { if(obj["port"].toInt() != SSDPServer::getJsonServerPort()) @@ -93,6 +103,14 @@ void SSDPHandler::handleSettingsUpdate(settings::type type, const QJsonDocument& } } + if(type == settings::WEBSERVER) + { + if(obj["sslPort"].toInt() != SSDPServer::getSSLServerPort()) + { + SSDPServer::setSSLServerPort(obj["sslPort"].toInt()); + } + } + if (type == settings::GENERAL) { if (obj["name"].toString() != SSDPServer::getHyperionName()) @@ -199,7 +217,21 @@ QString SSDPHandler::buildDesc() const /// %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 (%1)").arg(_localAddress), QString(HYPERION_VERSION), _uuid); + /// %5 json port 19444 + /// %6 ssl server port 8092 + /// %7 protobuf port 19445 + /// %8 flatbuf port 19400 + + return SSDP_DESCRIPTION.arg( + getBaseAddress(), + QString("Hyperion (%1)").arg(_localAddress), + QString(HYPERION_VERSION), + _uuid, + QString::number(SSDPServer::getJsonServerPort()), + QString::number(SSDPServer::getSSLServerPort()), + QString::number(SSDPServer::getProtoBufPort()), + QString::number(SSDPServer::getFlatBufPort()) + ); } void SSDPHandler::sendAnnounceList(bool alive) diff --git a/libsrc/webserver/QtHttpClientWrapper.cpp b/libsrc/webserver/QtHttpClientWrapper.cpp index 37b148a6..68bca8e0 100644 --- a/libsrc/webserver/QtHttpClientWrapper.cpp +++ b/libsrc/webserver/QtHttpClientWrapper.cpp @@ -153,7 +153,7 @@ void QtHttpClientWrapper::onClientDataReceived (void) case RequestParsed: // a valid request has ben fully parsed { // Catch websocket header "Upgrade" - if(m_currentRequest->getHeader(QtHttpHeader::Upgrade) == "websocket") + if(m_currentRequest->getHeader(QtHttpHeader::Upgrade).toLower() == "websocket") { if(m_websocketClient == Q_NULLPTR) { diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 30fbe711..197518e5 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -302,7 +302,12 @@ void HyperionDaemon::startNetworkServices() sslWsThread->start(); // Create SSDP server in thread - _ssdp = new SSDPHandler(_webserver, getSetting(settings::FLATBUFSERVER).object()["port"].toInt(), getSetting(settings::JSONSERVER).object()["port"].toInt(), getSetting(settings::GENERAL).object()["name"].toString()); + _ssdp = new SSDPHandler(_webserver, + getSetting(settings::FLATBUFSERVER).object()["port"].toInt(), + getSetting(settings::PROTOSERVER).object()["port"].toInt(), + getSetting(settings::JSONSERVER).object()["port"].toInt(), + getSetting(settings::WEBSERVER).object()["sslPort"].toInt(), + getSetting(settings::GENERAL).object()["name"].toString()); QThread *ssdpThread = new QThread(this); ssdpThread->setObjectName("SSDPThread"); _ssdp->moveToThread(ssdpThread); From c09061e5d8bcddd9ae82ca02a42c028d8320ccb5 Mon Sep 17 00:00:00 2001 From: The-Master777 Date: Sun, 18 Oct 2020 17:16:27 +0200 Subject: [PATCH 2/9] boblight: reduce cpu time spent on memcopy and parsing rgb values (#1016) --- include/utils/QStringUtils.h | 36 ++- .../BoblightClientConnection.cpp | 209 +++++++++++++++--- .../boblightserver/BoblightClientConnection.h | 40 ++++ 3 files changed, 255 insertions(+), 30 deletions(-) diff --git a/include/utils/QStringUtils.h b/include/utils/QStringUtils.h index 52d245c0..6b302d0a 100644 --- a/include/utils/QStringUtils.h +++ b/include/utils/QStringUtils.h @@ -3,6 +3,8 @@ #include #include +#include +#include namespace QStringUtils { @@ -13,11 +15,11 @@ enum class SplitBehavior { inline QStringList split (const QString &string, const QString &sep, SplitBehavior behavior = SplitBehavior::KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive) { - #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) return behavior == SplitBehavior::SkipEmptyParts ? string.split(sep, Qt::SkipEmptyParts , cs) : string.split(sep, Qt::KeepEmptyParts , cs); - #else +#else return behavior == SplitBehavior::SkipEmptyParts ? string.split(sep, QString::SkipEmptyParts , cs) : string.split(sep, QString::KeepEmptyParts , cs); - #endif +#endif } inline QStringList split (const QString &string, QChar sep, SplitBehavior behavior = SplitBehavior::KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive) @@ -37,6 +39,34 @@ inline QStringList split (const QString &string, const QRegExp &rx, SplitBehavio return behavior == SplitBehavior::SkipEmptyParts ? string.split(rx, QString::SkipEmptyParts) : string.split(rx, QString::KeepEmptyParts); #endif } + +inline QVector splitRef(const QString &string, const QString &sep, SplitBehavior behavior = SplitBehavior::KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + return string.splitRef(sep, behavior == SplitBehavior::SkipEmptyParts ? Qt::SkipEmptyParts : Qt::KeepEmptyParts , cs); +#else + return string.splitRef(sep, behavior == SplitBehavior::SkipEmptyParts ? QString::SkipEmptyParts : QString::KeepEmptyParts, cs); +#endif +} + +inline QVector splitRef(const QString &string, QChar sep, SplitBehavior behavior = SplitBehavior::KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + return string.splitRef(sep, behavior == SplitBehavior::SkipEmptyParts ? Qt::SkipEmptyParts : Qt::KeepEmptyParts, cs); +#else + return string.splitRef(sep, behavior == SplitBehavior::SkipEmptyParts ? QString::SkipEmptyParts : QString::KeepEmptyParts, cs); +#endif +} + +inline QVector splitRef(const QString &string, const QRegExp &rx, SplitBehavior behavior = SplitBehavior::KeepEmptyParts) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + return string.splitRef(rx,behavior == SplitBehavior::SkipEmptyParts ? Qt::SkipEmptyParts : Qt::KeepEmptyParts); +#else + return string.splitRef(rx, behavior == SplitBehavior::SkipEmptyParts ? QString::SkipEmptyParts : QString::KeepEmptyParts); +#endif +} + } #endif // QSTRINGUTILS_H diff --git a/libsrc/boblightserver/BoblightClientConnection.cpp b/libsrc/boblightserver/BoblightClientConnection.cpp index 485f2b52..e50a6dc9 100644 --- a/libsrc/boblightserver/BoblightClientConnection.cpp +++ b/libsrc/boblightserver/BoblightClientConnection.cpp @@ -3,11 +3,13 @@ #include #include #include +#include // stl includes #include #include #include +#include // Qt includes #include @@ -54,18 +56,19 @@ BoblightClientConnection::~BoblightClientConnection() void BoblightClientConnection::readData() { - _receiveBuffer += _socket->readAll(); + _receiveBuffer.append(_socket->readAll()); int bytes = _receiveBuffer.indexOf('\n') + 1; while(bytes > 0) { // create message string (strip the newline) - QString message = QString::fromLatin1(_receiveBuffer.data(), bytes-1); - // remove message data from buffer - _receiveBuffer = _receiveBuffer.mid(bytes); + const QString message = readMessage(_receiveBuffer.data(), bytes); // handle trimmed message - handleMessage(message.trimmed()); + handleMessage(message); + + // remove message data from buffer + _receiveBuffer.remove(0, bytes); // drop messages if the buffer is too full if (_receiveBuffer.size() > 100*1024) @@ -79,6 +82,31 @@ void BoblightClientConnection::readData() } } +QString BoblightClientConnection::readMessage(const char *data, const size_t size) const +{ + char *end = (char *)data + size - 1; + + // Trim left + while (data < end && std::isspace(*data)) + { + ++data; + } + + // Trim right + while (end > data && std::isspace(*end)) + { + --end; + } + + // create message string (strip the newline) + const int len = end - data + 1; + const QString message = QString::fromLatin1(data, len); + + //std::cout << bytes << ": \"" << message.toUtf8().constData() << "\"" << std::endl; + + return message; +} + void BoblightClientConnection::socketClosed() { // clear the current channel @@ -88,10 +116,11 @@ void BoblightClientConnection::socketClosed() emit connectionClosed(this); } + void BoblightClientConnection::handleMessage(const QString & message) { //std::cout << "boblight message: " << message.toStdString() << std::endl; - QStringList messageParts = QStringUtils::split(message," ",QStringUtils::SplitBehavior::SkipEmptyParts); + const QVector messageParts = QStringUtils::splitRef(message, ' ', QStringUtils::SplitBehavior::SkipEmptyParts); if (messageParts.size() > 0) { if (messageParts[0] == "hello") @@ -122,32 +151,18 @@ void BoblightClientConnection::handleMessage(const QString & message) if (messageParts.size() > 3 && messageParts[1] == "light") { bool rc; - unsigned ledIndex = messageParts[2].toUInt(&rc); + const unsigned ledIndex = parseUInt(messageParts[2], &rc); if (rc && ledIndex < _ledColors.size()) { if (messageParts[3] == "rgb" && messageParts.size() == 7) { - // replace decimal comma with decimal point - messageParts[4].replace(',', '.'); - messageParts[5].replace(',', '.'); - messageParts[6].replace(',', '.'); + // custom parseByte accepts both ',' and '.' as decimal separator + // no need to replace decimal comma with decimal point bool rc1, rc2, rc3; - uint8_t red = qMax(0, qMin(255, int(255 * messageParts[4].toFloat(&rc1)))); - - // check for correct locale should not be needed anymore - please check! - if (!rc1) - { - // maybe a locale issue. switch to a locale with a comma instead of a dot as decimal seperator (or vice versa) - _locale = QLocale((_locale.decimalPoint() == QChar('.')) ? QLocale::Dutch : QLocale::C); - _locale.setNumberOptions(QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator); - - // try again - red = qMax(0, qMin(255, int(255 * messageParts[4].toFloat(&rc1)))); - } - - uint8_t green = qMax(0, qMin(255, int(255 * messageParts[5].toFloat(&rc2)))); - uint8_t blue = qMax(0, qMin(255, int(255 * messageParts[6].toFloat(&rc3)))); + const uint8_t red = parseByte(messageParts[4], &rc1); + const uint8_t green = parseByte(messageParts[5], &rc2); + const uint8_t blue = parseByte(messageParts[6], &rc3); if (rc1 && rc2 && rc3) { @@ -181,7 +196,7 @@ void BoblightClientConnection::handleMessage(const QString & message) else if (messageParts.size() == 3 && messageParts[1] == "priority") { bool rc; - int prio = messageParts[2].toInt(&rc); + const int prio = static_cast(parseUInt(messageParts[2], &rc)); if (rc && prio != _priority) { if (_priority != 0 && _hyperion->getPriorityInfo(_priority).componentId == hyperion::COMP_BOBLIGHTSERVER) @@ -223,6 +238,146 @@ void BoblightClientConnection::handleMessage(const QString & message) Debug(_log, "unknown boblight message: %s", QSTRING_CSTR(message)); } +/// Float values 10 to the power of -p for p in 0 .. 8. +const float ipows[] = { + 1, + 1.0f / 10.0f, + 1.0f / 100.0f, + 1.0f / 1000.0f, + 1.0f / 10000.0f, + 1.0f / 100000.0f, + 1.0f / 1000000.0f, + 1.0f / 10000000.0f, + 1.0f / 100000000.0f}; + +float BoblightClientConnection::parseFloat(const QStringRef& s, bool *ok) const +{ + // We parse radix 10 + const char MIN_DIGIT = '0'; + const char MAX_DIGIT = '9'; + const char SEP_POINT = '.'; + const char SEP_COMMA = ','; + const int NUM_POWS = 9; + + /// The maximum number of characters we want to process + const int MAX_LEN = 18; // Chosen randomly + + /// The index of the current character + int q = 0; + + /// The integer part of the number + int64_t n = 0; + + auto it = s.begin(); + +#define STEP ((it != s.end()) && (q++ < MAX_LEN)) + + // parse the integer-part + while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && STEP) + { + n = (n * 10) + (it->unicode() - MIN_DIGIT); + ++it; + } + + /// The resulting float value + float f = static_cast(n); + + // parse decimal part + if ((it->unicode() == SEP_POINT || it->unicode() == SEP_COMMA) && STEP) + { + /// The decimal part of the number + int64_t d = 0; + + /// The exponent for the scale-factor 10 to the power -e + int e = 0; + + ++it; + while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && STEP) + { + d = (d * 10) + (it->unicode() - MIN_DIGIT); + ++e; + ++it; + } + + const float h = static_cast(d); + + // We want to use pre-calculated power whenever possible + if (e < NUM_POWS) + { + f += h * ipows[e]; + } + else + { + f += h / std::pow(10.0f, e); + } + } + + if (q >= MAX_LEN || q < s.length()) + { + if (ok) + { + //std::cout << "FAIL L " << q << ": " << s.toUtf8().constData() << std::endl; + *ok = false; + } + return 0; + } + + if (ok) + { + //std::cout << "OK " << d << ": " << s.toUtf8().constData() << std::endl; + *ok = true; + } + + return f; +} + +unsigned BoblightClientConnection::parseUInt(const QStringRef& s, bool *ok) const +{ + // We parse radix 10 + const char MIN_DIGIT = '0'; + const char MAX_DIGIT = '9'; + + /// The maximum number of characters we want to process + const int MAX_LEN = 10; + + /// The index of the current character + int q = 0; + + /// The integer part of the number + int n = 0; + + auto it = s.begin(); + + // parse the integer-part + while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && ((it != s.end()) && (q++ < MAX_LEN))) + { + n = (n * 10) + (it->unicode() - MIN_DIGIT); + ++it; + } + + if (ok) + { + *ok = !(q >= MAX_LEN || q < s.length()); + } + + return n; +} + +uint8_t BoblightClientConnection::parseByte(const QStringRef& s, bool *ok) const +{ + const int LO = 0; + const int HI = 255; + +#if defined(FAST_FLOAT_PARSE) + const float d = parseFloat(s, ok); +#else + const float d = s.toFloat(ok); +#endif + + // Clamp to byte range 0 to 255 + return static_cast(qBound(LO, int(HI * d), HI)); // qBound args are in order min, value, max; see: https://doc.qt.io/qt-5/qtglobal.html#qBound +} + void BoblightClientConnection::sendLightMessage() { char buffer[256]; diff --git a/libsrc/boblightserver/BoblightClientConnection.h b/libsrc/boblightserver/BoblightClientConnection.h index 54b89efc..46a3b0eb 100644 --- a/libsrc/boblightserver/BoblightClientConnection.h +++ b/libsrc/boblightserver/BoblightClientConnection.h @@ -4,11 +4,15 @@ #include #include #include +#include // utils includes #include #include +/// Whether to parse floats with an eye on performance +#define FAST_FLOAT_PARSE + class ImageProcessor; class Hyperion; @@ -70,6 +74,42 @@ private: /// void sendLightMessage(); + /// + /// Interpret the float value "0.0" to "1.0" of the QString byte values 0 .. 255 + /// + /// @param s the string to parse + /// @param ok whether the result is ok + /// @return the parsed byte value in range 0 to 255, or 0 + /// + uint8_t parseByte(const QStringRef& s, bool *ok = nullptr) const; + + /// + /// Parse the given QString as unsigned int value. + /// + /// @param s the string to parse + /// @param ok whether the result is ok + /// @return the parsed unsigned int value + /// + unsigned parseUInt(const QStringRef& s, bool *ok = nullptr) const; + + /// + /// Parse the given QString as float value, e.g. the 16-bit (wide char) QString "1" shall represent 1, "0.5" is 0.5 and so on. + /// + /// @param s the string to parse + /// @param ok whether the result is ok + /// @return the parsed float value, or 0 + /// + float parseFloat(const QStringRef& s, bool *ok = nullptr) const; + + /// + /// Read an incoming boblight message as QString + /// + /// @param data the char data buffer of the incoming message + /// @param size the length of the buffer buffer + /// @returns the incoming boblight message as QString + /// + QString readMessage(const char *data, const size_t size) const; + private: /// Locale used for parsing floating point values QLocale _locale; From 02d0ab68f62b6d11e1298ee97eb5b68777fee719 Mon Sep 17 00:00:00 2001 From: Denrage Date: Sun, 18 Oct 2020 17:18:30 +0200 Subject: [PATCH 3/9] FIX: display argument in hyperion-qt (#1027) --- src/hyperion-osx/hyperion-osx.cpp | 4 ++-- src/hyperion-qt/hyperion-qt.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hyperion-osx/hyperion-osx.cpp b/src/hyperion-osx/hyperion-osx.cpp index 4842c65d..bf2c08da 100644 --- a/src/hyperion-osx/hyperion-osx.cpp +++ b/src/hyperion-osx/hyperion-osx.cpp @@ -34,7 +34,7 @@ int main(int argc, char ** argv) // create the option parser and initialize all parameters Parser parser("OSX capture application for Hyperion. Will automatically search a Hyperion server if -a option isn't used. Please note that if you have more than one server running it's more or less random which one will be used."); - Option & argDisplay = parser.add