diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 68344d73..0d638c53 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -186,6 +186,7 @@ "conf_network_json_intro": "The JSON-RPC-Port of all Hyperion instances, used for remote control.", "conf_network_net_intro": "Network related settings which are applied to all network services.", "conf_network_proto_intro": "The PROTO-Port of all Hyperion instances, used for picture streams (HyperionScreenCap, Kodi Addon, Android Hyperion Grabber, ...)", + "conf_network_tok_idhead": "ID", "conf_network_tok_cidhead": "Description", "conf_network_tok_comment_title": "Token description", "conf_network_tok_desc": "Tokens grant other applications access to the Hyperion API, an application can request a token where you need to accept it or you create them on your own below. These tokens are just required when \"API Authorization\" is enabled in network settings.", @@ -500,19 +501,16 @@ "edt_conf_log_level_expl": "Depending on loglevel you see less or more messages in your log.", "edt_conf_log_level_title": "Log-Level", "edt_conf_net_apiAuth_expl": "Enforce all applications that use the Hyperion API to authenticate themself against Hyperion (Exception: see \"Local API Authentication\"). Higher security, as you control the access and revoke it at any time.", - "edt_conf_net_apiAuth_title": "API Authentication", "edt_conf_net_heading_title": "Network", - "edt_conf_net_internetAccessAPI_expl": "Allow access to the Hyperion API/Webinterface from the internet. Disable for higher security.", + "edt_conf_net_internetAccessAPI_expl": "Allow access to the Hyperion API/Web Interface from the Internet. Disable for increased security.", "edt_conf_net_internetAccessAPI_title": "Internet API Access", - "edt_conf_net_ipWhitelist_expl": "You can whitelist IP addresses instead allowing all connections from internet to connect to the Hyperion API/Webinterface.", - "edt_conf_net_ipWhitelist_title": "Whitelisted IP's", + "edt_conf_net_ipWhitelist_expl": "Define whitelisted IP addresses from which API requests from the Internet are allowed. All other external connections will be denied.", + "edt_conf_net_ipWhitelist_title": "Whitelisted IP addresses", "edt_conf_net_ip_itemtitle": "IP", - "edt_conf_net_localAdminAuth_expl": "When enabled, administration access from your local network needs a password.", - "edt_conf_net_localAdminAuth_title": "Local Admin API Authentication", - "edt_conf_net_localApiAuth_expl": "When enabled, connections from your home network needs to authenticate themselves against Hyperion with a token.", + "edt_conf_net_localApiAuth_expl": "When disabled, API authorisation via password or token is not required for local connections. The exception is administrative commands.", "edt_conf_net_localApiAuth_title": "Local API Authentication", - "edt_conf_net_restirctedInternetAccessAPI_expl": "You can restrict the access to the API through the internet to certain IP's.", - "edt_conf_net_restirctedInternetAccessAPI_title": "Restrict to IP's", + "edt_conf_net_restirctedInternetAccessAPI_expl": "You can restrict API requests over the Internet to only those IP addresses on the whitelist.", + "edt_conf_net_restirctedInternetAccessAPI_title": "Restrict to IP addresses", "edt_conf_os_events_lockEnable_title": "Listen to lock events", "edt_conf_os_events_lockEnable_expl": "Listen to screen lock/unlock events", "edt_conf_os_events_suspendEnable_title": "Listen to suspend events", diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js index e8ec28a8..00b1ba3d 100644 --- a/assets/webconfig/js/content_index.js +++ b/assets/webconfig/js/content_index.js @@ -73,26 +73,30 @@ $(document).ready(function () { //End language selection $(window.hyperion).on("cmd-authorize-tokenRequest cmd-authorize-getPendingTokenRequests", function (event) { - var val = event.response.info; - if (Array.isArray(event.response.info)) { - if (event.response.info.length == 0) { - return - } - val = event.response.info[0] - if (val.comment == '') - $('#modal_dialog').modal('hide'); - } - showInfoDialog("grantToken", $.i18n('conf_network_tok_grantT'), $.i18n('conf_network_tok_grantMsg') + '
App: ' + val.comment + '
Code: ' + val.id + '') - $("#tok_grant_acc").off().on('click', function () { - tokenList.push(val) - // forward event, in case we need to rebuild the list now - $(window.hyperion).trigger({ type: "build-token-list" }); - requestHandleTokenRequest(val.id, true) - }); - $("#tok_deny_acc").off().on('click', function () { - requestHandleTokenRequest(val.id, false) - }); + if (event.response && event.response.info !== undefined) { + var val = event.response.info; + + if (Array.isArray(event.response.info)) { + if (event.response.info.length == 0) { + return + } + val = event.response.info[0] + if (val.comment == '') + $('#modal_dialog').modal('hide'); + } + + showInfoDialog("grantToken", $.i18n('conf_network_tok_grantT'), $.i18n('conf_network_tok_grantMsg') + '
App: ' + val.comment + '
Code: ' + val.id + '') + $("#tok_grant_acc").off().on('click', function () { + tokenList.push(val) + // forward event, in case we need to rebuild the list now + $(window.hyperion).trigger({ type: "build-token-list" }); + requestHandleTokenRequest(val.id, true) + }); + $("#tok_deny_acc").off().on('click', function () { + requestHandleTokenRequest(val.id, false) + }); + } }); $(window.hyperion).one("cmd-authorize-getTokenList", function (event) { diff --git a/assets/webconfig/js/content_logging.js b/assets/webconfig/js/content_logging.js index 7a9c791d..3fbe7aef 100644 --- a/assets/webconfig/js/content_logging.js +++ b/assets/webconfig/js/content_logging.js @@ -3,10 +3,13 @@ var createdCont = false; var isScroll = true; performTranslation(); -requestLoggingStop(); $(document).ready(function () { + window.addEventListener('hashchange', function(event) { + requestLoggingStop(); + }); + requestLoggingStart(); $('#conf_cont').append(createOptPanel('fa-reorder', $.i18n("edt_conf_log_heading_title"), 'editor_container', 'btn_submit')); @@ -180,7 +183,7 @@ $(document).ready(function () { $(window.hyperion).on("cmd-logging-update", function (event) { - var messages = (event.response.result.messages); + var messages = (event.response.data.messages); if (messages.length != 0) { if (!createdCont) { diff --git a/assets/webconfig/js/content_network.js b/assets/webconfig/js/content_network.js index 26fec2c8..9f9f68c3 100644 --- a/assets/webconfig/js/content_network.js +++ b/assets/webconfig/js/content_network.js @@ -213,13 +213,13 @@ $(document).ready(function () { for (var key in tokenList) { var lastUse = (tokenList[key].last_use) ? tokenList[key].last_use : "-"; var btn = ''; - $('.tktbody').append(createTableRow([tokenList[key].comment, lastUse, btn], false, true)); + $('.tktbody').append(createTableRow([tokenList[key].id, tokenList[key].comment, lastUse, btn], false, true)); $('#tok' + tokenList[key].id).off().on('click', handleDeleteToken); } } createTable('tkthead', 'tktbody', 'tktable'); - $('.tkthead').html(createTableRow([$.i18n('conf_network_tok_cidhead'), $.i18n('conf_network_tok_lastuse'), $.i18n('general_btn_delete')], true, true)); + $('.tkthead').html(createTableRow([$.i18n('conf_network_tok_idhead'), $.i18n('conf_network_tok_cidhead'), $.i18n('conf_network_tok_lastuse'), $.i18n('general_btn_delete')], true, true)); buildTokenList(); function handleDeleteToken(e) { diff --git a/assets/webconfig/js/ledsim.js b/assets/webconfig/js/ledsim.js index 32a8898b..af633ba7 100644 --- a/assets/webconfig/js/ledsim.js +++ b/assets/webconfig/js/ledsim.js @@ -261,7 +261,7 @@ $(document).ready(function () { $("body").get(0).style.setProperty("--background-var", "none"); } else { - printLedsToCanvas(event.response.result.leds) + printLedsToCanvas(event.response.data.leds) $("body").get(0).style.setProperty("--background-var", "url(" + ($('#leds_preview_canv')[0]).toDataURL("image/jpg") + ") no-repeat top left"); } }); @@ -275,7 +275,7 @@ $(document).ready(function () { } } else { - var imageData = (event.response.result.image); + var imageData = (event.response.data.image); var image = new Image(); image.onload = function () { diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 18fc9dc9..f126b638 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -220,9 +220,7 @@ "internetAccessAPI": false, "restirctedInternetAccessAPI": false, "ipWhitelist": [], - "apiAuth": true, - "localApiAuth": false, - "localAdminAuth": true + "localApiAuth": false }, "ledConfig": { diff --git a/include/api/API.h b/include/api/API.h index 31a60f97..f50f5d2c 100644 --- a/include/api/API.h +++ b/include/api/API.h @@ -14,11 +14,15 @@ // qt includes #include -class QTimer; -class JsonCB; +class JsonCallbacks; class HyperionIManager; -const QString NO_AUTH = "No Authorization"; +// Constants +namespace { + +const char NO_AUTHORIZATION[] = "No Authorization";; + +} /// /// @brief API for Hyperion to be inherted from a child class with specific protocol implementations @@ -31,205 +35,208 @@ const QString NO_AUTH = "No Authorization"; class API : public QObject { - Q_OBJECT + Q_OBJECT public: #include - // workaround Q_ARG std::map template issues - typedef std::map MapRegister; - typedef QMap MapAuthDefs; - /// - /// Constructor - /// - ///@ param The parent Logger - /// @param localConnection Is this a local network connection? Use utils/NetOrigin to check that - /// @param parent Parent QObject - /// - API(Logger *log, bool localConnection, QObject *parent); - -protected: - /// - /// @brief Initialize the API - /// This call is REQUIRED! - /// - void init(); - - /// - /// @brief Set a single color - /// @param[in] priority The priority of the written color - /// @param[in] ledColor The color to write to the leds - /// @param[in] timeout_ms The time the leds are set to the given color [ms] - /// @param[in] origin The setter - /// - void setColor(int priority, const std::vector &ledColors, int timeout_ms = -1, const QString &origin = "API", hyperion::Components callerComp = hyperion::COMP_INVALID); - - /// - /// @brief Set a image - /// @param[in] data The command data - /// @param[in] comp The component that should be used - /// @param[out] replyMsg The replyMsg on failure - /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF - /// @return True on success - /// - bool setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID); - - /// - /// @brief Clear a priority in the Muxer, if -1 all priorities are cleared - /// @param priority The priority to clear - /// @param replyMsg the message on failure - /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF - /// @return True on success - /// - bool clearPriority(int priority, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID); - - /// - /// @brief Set a new component state - /// @param comp The component name - /// @param compState The new state of the comp - /// @param replyMsg The reply on failure - /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF - /// @ return True on success - /// - bool setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID); - - /// - /// @brief Set a ledToImageMapping type - /// @param type mapping type string - /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF - /// - void setLedMappingType(int type, hyperion::Components callerComp = hyperion::COMP_INVALID); - - /// - /// @brief Set the 2D/3D modes type - /// @param mode The VideoMode - /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF - /// - void setVideoMode(VideoMode mode, hyperion::Components callerComp = hyperion::COMP_INVALID); - -#if defined(ENABLE_EFFECTENGINE) - /// - /// @brief Set an effect - /// @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 - /// - bool setEffect(const EffectCmdData &dat, hyperion::Components callerComp = hyperion::COMP_INVALID); -#endif - - /// - /// @brief Set source auto select enabled or disabled - /// @param sate The new state - /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF - /// - void setSourceAutoSelect(bool state, hyperion::Components callerComp = hyperion::COMP_INVALID); - - /// - /// @brief Set the visible priority to given priority - /// @param priority The priority to set - /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF - /// - void setVisiblePriority(int priority, hyperion::Components callerComp = hyperion::COMP_INVALID); - - /// - /// @brief Register a input or update the meta data of a previous register call - /// ATTENTION: Check unregisterInput() method description !!! - /// @param[in] priority The priority of the channel - /// @param[in] component The component of the channel - /// @param[in] origin Who set the channel (CustomString@IP) - /// @param[in] owner Specific owner string, might be empty - /// @param[in] callerComp The component that call this (e.g. PROTO/FLAT) - /// - void registerInput(int priority, hyperion::Components component, const QString &origin, const QString &owner, hyperion::Components callerComp); - - /// - /// @brief Revoke a registerInput() call by priority. We maintain all registered priorities in this scope - /// ATTENTION: This is MANDATORY if you change (priority change) or stop(clear/timeout) DURING lifetime. If this class destructs it's not needed - /// @param priority The priority to unregister - /// - void unregisterInput(int priority); - - /// - /// @brief Handle the instance switching - /// @param inst The requested instance - /// @return True on success else false - /// - bool setHyperionInstance(quint8 inst); + // workaround Q_ARG std::map template issues + typedef std::map MapRegister; + typedef QMap MapAuthDefs; /// - /// @brief Check if Hyperion ist enabled - /// @return True when enabled else false - /// - bool isHyperionEnabled(); + /// Constructor + /// + ///@ param The parent Logger + /// @param localConnection Is this a local network connection? Use utils/NetOrigin to check that + /// @param parent Parent QObject + /// + API(Logger *log, bool localConnection, QObject *parent); - /// - /// @brief Get all instances data - /// @return The instance data - /// - QVector getAllInstanceData(); +protected: + /// + /// @brief Initialize the API + /// This call is REQUIRED! + /// + void init(); - /// - /// @brief Start instance - /// @param index The instance index - /// @param tan The tan - /// @return True on success else false - /// - bool startInstance(quint8 index, int tan = 0); + virtual void stopDataConnections() = 0; - /// - /// @brief Stop instance - /// @param index The instance index - /// - void stopInstance(quint8 index); + /// + /// @brief Set a single color + /// @param[in] priority The priority of the written color + /// @param[in] ledColor The color to write to the leds + /// @param[in] timeout_ms The time the leds are set to the given color [ms] + /// @param[in] origin The setter + /// + void setColor(int priority, const std::vector &ledColors, int timeout_ms = -1, const QString &origin = "API", hyperion::Components callerComp = hyperion::COMP_INVALID); - ////////////////////////////////// - /// AUTH / ADMINISTRATION METHODS - ////////////////////////////////// + /// + /// @brief Set a image + /// @param[in] data The command data + /// @param[in] comp The component that should be used + /// @param[out] replyMsg The replyMsg on failure + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// @return True on success + /// + bool setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID); - /// - /// @brief Delete instance. Requires ADMIN ACCESS - /// @param index The instance index - /// @param replyMsg The reply Msg - /// @return False with reply - /// - bool deleteInstance(quint8 index, QString &replyMsg); + /// + /// @brief Clear a priority in the Muxer, if -1 all priorities are cleared + /// @param priority The priority to clear + /// @param replyMsg the message on failure + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// @return True on success + /// + bool clearPriority(int priority, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID); - /// - /// @brief Create instance. Requires ADMIN ACCESS - /// @param name With given name - /// @return False with reply - /// - QString createInstance(const QString &name); + /// + /// @brief Set a new component state + /// @param comp The component name + /// @param compState The new state of the comp + /// @param replyMsg The reply on failure + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// @ return True on success + /// + bool setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID); - /// - /// @brief Rename an instance. Requires ADMIN ACCESS - /// @param index The instance index - /// @param name With given name - /// @return False with reply - /// - QString setInstanceName(quint8 index, const QString &name); + /// + /// @brief Set a ledToImageMapping type + /// @param type mapping type string + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// + void setLedMappingType(int type, hyperion::Components callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Set the 2D/3D modes type + /// @param mode The VideoMode + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// + void setVideoMode(VideoMode mode, hyperion::Components callerComp = hyperion::COMP_INVALID); #if defined(ENABLE_EFFECTENGINE) - /// - /// @brief Delete an effect. Requires ADMIN ACCESS - /// @param name The effect name - /// @return True on success else false - /// - QString deleteEffect(const QString &name); - - /// - /// @brief Delete an effect. Requires ADMIN ACCESS - /// @param name The effect name - /// @return True on success else false - /// - QString saveEffect(const QJsonObject &data); + /// + /// @brief Set an effect + /// @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 + /// + bool setEffect(const EffectCmdData &dat, hyperion::Components callerComp = hyperion::COMP_INVALID); #endif - /// - /// @brief Save settings object. Requires ADMIN ACCESS - /// @param data The data object - /// + /// + /// @brief Set source auto select enabled or disabled + /// @param sate The new state + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// + void setSourceAutoSelect(bool state, hyperion::Components callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Set the visible priority to given priority + /// @param priority The priority to set + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// + void setVisiblePriority(int priority, hyperion::Components callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Register a input or update the meta data of a previous register call + /// ATTENTION: Check unregisterInput() method description !!! + /// @param[in] priority The priority of the channel + /// @param[in] component The component of the channel + /// @param[in] origin Who set the channel (CustomString@IP) + /// @param[in] owner Specific owner string, might be empty + /// @param[in] callerComp The component that call this (e.g. PROTO/FLAT) + /// + void registerInput(int priority, hyperion::Components component, const QString &origin, const QString &owner, hyperion::Components callerComp); + + /// + /// @brief Revoke a registerInput() call by priority. We maintain all registered priorities in this scope + /// ATTENTION: This is MANDATORY if you change (priority change) or stop(clear/timeout) DURING lifetime. If this class destructs it's not needed + /// @param priority The priority to unregister + /// + void unregisterInput(int priority); + + /// + /// @brief Handle the instance switching + /// @param inst The requested instance + /// @return True on success else false + /// + bool setHyperionInstance(quint8 inst); + + /// + /// @brief Check if Hyperion ist enabled + /// @return True when enabled else false + /// + bool isHyperionEnabled(); + + /// + /// @brief Get all instances data + /// @return The instance data + /// + QVector getAllInstanceData() const; + + /// + /// @brief Start instance + /// @param index The instance index + /// @param tan The tan + /// @return True on success else false + /// + bool startInstance(quint8 index, int tan = 0); + + /// + /// @brief Stop instance + /// @param index The instance index + /// + void stopInstance(quint8 index); + + ////////////////////////////////// + /// AUTH / ADMINISTRATION METHODS + ////////////////////////////////// + + /// + /// @brief Delete instance. Requires ADMIN ACCESS + /// @param index The instance index + /// @param replyMsg The reply Msg + /// @return False with reply + /// + bool deleteInstance(quint8 index, QString &replyMsg); + + /// + /// @brief Create instance. Requires ADMIN ACCESS + /// @param name With given name + /// @return False with reply + /// + QString createInstance(const QString &name); + + /// + /// @brief Rename an instance. Requires ADMIN ACCESS + /// @param index The instance index + /// @param name With given name + /// @return False with reply + /// + QString setInstanceName(quint8 index, const QString &name); + +#if defined(ENABLE_EFFECTENGINE) + /// + /// @brief Delete an effect. Requires ADMIN ACCESS + /// @param name The effect name + /// @return True on success else false + /// + QString deleteEffect(const QString &name); + + /// + /// @brief Delete an effect. Requires ADMIN ACCESS + /// @param name The effect name + /// @return True on success else false + /// + QString saveEffect(const QJsonObject &data); +#endif + + /// + /// @brief Save settings object. Requires ADMIN ACCESS + /// @param data The data object + /// bool saveSettings(const QJsonObject &data); /// @@ -238,171 +245,189 @@ protected: /// bool restoreSettings(const QJsonObject &data); - /// - /// @brief Test if we are authorized to use the interface - /// @return The result - /// - bool isAuthorized() { return _authorized; }; + /// + /// @brief Set the authorizationn state + /// @param authorized True, if authorized + /// + void setAuthorization(bool authorized) { _authorized = authorized; } - /// - /// @brief Test if we are authorized to use the admin interface - /// @return The result - /// - bool isAdminAuthorized() { return _adminAuthorized; }; + /// + /// @brief Test if we are authorized to use the interface + /// @return The result + /// + bool isAuthorized() const { return _authorized; } - /// - /// @brief Update the Password of Hyperion. Requires ADMIN ACCESS - /// @param password Old password - /// @param newPassword New password - /// @return True on success else false - /// - bool updateHyperionPassword(const QString &password, const QString &newPassword); + /// + /// @brief Set the authorizationn state for admin activities + /// @param authorized True, if authorized + /// + void setAdminAuthorization(bool adminAuthorized) { _adminAuthorized = adminAuthorized; } - /// - /// @brief Get a new token from AuthManager. Requires ADMIN ACCESS - /// @param comment The comment of the request - /// @param def The final definition - /// @return Empty string on success else error message - /// - QString createToken(const QString &comment, AuthManager::AuthDefinition &def); + /// + /// @brief Test if we are authorized to use admin activites + /// @return The result + /// + bool isAdminAuthorized() const { return _adminAuthorized; } - /// - /// @brief Rename a token by given id. Requires ADMIN ACCESS - /// @param id The id of the token - /// @param comment The new comment - /// @return Empty string on success else error message - /// - QString renameToken(const QString &id, const QString &comment); + /// + /// @brief Return, if connection is from local network segment + /// @return The result + /// + bool islocalConnection() const { return _localConnection; } - /// - /// @brief Delete a token by given id. Requires ADMIN ACCESS - /// @param id The id of the token - /// @return Empty string on success else error message - /// - QString deleteToken(const QString &id); + /// + /// @brief Update the Password of Hyperion. Requires ADMIN ACCESS + /// @param password Old password + /// @param newPassword New password + /// @return True on success else false + /// + bool updateHyperionPassword(const QString &password, const QString &newPassword); - /// - /// @brief Set a new token request - /// @param comment The comment - /// @param id The id + /// + /// @brief Get a new token from AuthManager. Requires ADMIN ACCESS + /// @param comment The comment of the request + /// @param def The final definition + /// @return Empty string on success else error message + /// + QString createToken(const QString &comment, AuthManager::AuthDefinition &def); + + /// + /// @brief Rename a token by given id. Requires ADMIN ACCESS + /// @param tokenId The id of the token + /// @param comment The new comment + /// @return Empty string on success else error message + /// + QString renameToken(const QString &tokenId, const QString &comment); + + /// + /// @brief Delete a token by given id. Requires ADMIN ACCESS + /// @param tokenId The id of the token + /// @return Empty string on success else error message + /// + QString deleteToken(const QString &tokenId); + + /// + /// @brief Set a new token request + /// @param comment The comment + /// @param tokenId The id of the token /// @param tan The tan - /// - void setNewTokenRequest(const QString &comment, const QString &id, const int &tan); + /// + void setNewTokenRequest(const QString &comment, const QString &tokenId, const int &tan); - /// - /// @brief Cancel new token request - /// @param comment The comment - /// @param id The id - /// - void cancelNewTokenRequest(const QString &comment, const QString &id); + /// + /// @brief Cancel new token request + /// @param comment The comment + /// @param tokenId The id of the token + /// + void cancelNewTokenRequest(const QString &comment, const QString &tokenId); - /// - /// @brief Handle a pending token request. Requires ADMIN ACCESS - /// @param id The id fo the request - /// @param accept True when it should be accepted, else false - /// @return True on success - bool handlePendingTokenRequest(const QString &id, bool accept); - - /// - /// @brief Get the current List of Tokens. Requires ADMIN ACCESS - /// @param def returns the defintions - /// @return True on success - /// - bool getTokenList(QVector &def); - - /// - /// @brief Get all current pending token requests. Requires ADMIN ACCESS - /// @return True on success - /// - bool getPendingTokenRequests(QVector &map); - - /// - /// @brief Is User Token Authorized. On success this will grant acces to API and ADMIN API - /// @param userToken The user Token - /// @return True on succes - /// - bool isUserTokenAuthorized(const QString &userToken); - - /// - /// @brief Get the current User Token (session token). Requires ADMIN ACCESS - /// @param userToken The user Token - /// @return True on success - /// - bool getUserToken(QString &userToken); - - /// - /// @brief Is a token authorized. On success this will grant acces to the API (NOT ADMIN API) - /// @param token The user Token + /// + /// @brief Handle a pending token request. Requires ADMIN ACCESS + /// @param tokenId The id fo the request + /// @param accept True when it should be accepted, else false /// @return True on success - /// - bool isTokenAuthorized(const QString &token); + bool handlePendingTokenRequest(const QString &tokenId, bool accept); - /// - /// @brief Is User authorized. On success this will grant acces to the API and ADMIN API - /// @param password The password of the User - /// @return True if authorized - /// - bool isUserAuthorized(const QString &password); + /// + /// @brief Get the current List of Tokens. Requires ADMIN ACCESS + /// @param def returns the defintions + /// @return True on success + /// + bool getTokenList(QVector &def); - /// - /// @brief Test if Hyperion has the default PW - /// @return The result - /// - bool hasHyperionDefaultPw(); + /// + /// @brief Get all current pending token requests. Requires ADMIN ACCESS + /// @return True on success + /// + bool getPendingTokenRequests(QVector &map); - /// - /// @brief Logout revokes all authorizations - /// - void logout(); + /// + /// @brief Is User Token Authorized. On success this will grant acces to API and ADMIN API + /// @param userToken The user Token + /// @return True on succes + /// + bool isUserTokenAuthorized(const QString &userToken); - /// Reflect auth status of this client - bool _authorized; - bool _adminAuthorized; + /// + /// @brief Get the current User Token (session token). Requires ADMIN ACCESS + /// @param userToken The user Token + /// @return True on success + /// + bool getUserToken(QString &userToken); - /// Is this a local connection - bool _localConnection; + /// + /// @brief Is a token authorized. On success this will grant acces to the API (NOT ADMIN API) + /// @param token The user Token + /// @return True on success + /// + bool isTokenAuthorized(const QString &token); - AuthManager *_authManager; - HyperionIManager *_instanceManager; + /// + /// @brief Is User authorized. On success this will grant acces to the API and ADMIN API + /// @param password The password of the User + /// @return True if authorized + /// + bool isUserAuthorized(const QString &password); - Logger *_log; - Hyperion *_hyperion; + /// + /// @brief Test if Hyperion has the default PW + /// @return The result + /// + bool hasHyperionDefaultPw(); + + /// + /// @brief Logout revokes all authorizations + /// + void logout(); + + + AuthManager *_authManager; + HyperionIManager *_instanceManager; + + Logger *_log; + Hyperion *_hyperion; signals: - /// - /// @brief The API might decide to block connections for security reasons, this emitter should close the socket - /// - void forceClose(); + /// + /// @brief The API might decide to block connections for security reasons, this emitter should close the socket + /// + void forceClose(); - /// - /// @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 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); + /// + /// @brief Emits whenever a new Token request is pending. This signal is just active when ADMIN ACCESS has been granted + /// @param tokenId 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 &tokenId, const QString &comment); - /// - /// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance it will emit. - /// @param success If true the request was accepted else false and no token was created - /// @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, const int &tan); + /// + /// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance it will emit. + /// @param success If true the request was accepted else false and no token was created + /// @param token The new token that is now valid + /// @param comment The comment that was part of the request + /// @param tokenId 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 &tokenId, 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); + /// + /// @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: - void stopDataConnectionss(); - // Contains all active register call data - std::map _activeRegisters; + /// Reflect authorization status of this client + bool _authorized; + bool _adminAuthorized; - // current instance index - quint8 _currInstanceIndex; + /// Is this a local connection + bool _localConnection; + + // Contains all active register call data + std::map _activeRegisters; + + // current instance index + quint8 _currInstanceIndex; }; diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h index 8e36b158..7cef7f14 100644 --- a/include/api/JsonAPI.h +++ b/include/api/JsonAPI.h @@ -2,19 +2,24 @@ // parent class #include +#include + #include // hyperion includes #include #include #include +#include // qt includes #include #include +#include +#include class QTimer; -class JsonCB; +class JsonCallbacks; class AuthManager; class JsonAPI : public API @@ -46,40 +51,24 @@ public: void initialize(); public slots: - /// - /// @brief Is called whenever the current Hyperion instance pushes new led raw values (if enabled) - /// @param ledColors The current led colors - /// - void streamLedcolorsUpdate(const std::vector &ledColors); - - /// - /// @brief Push images whenever hyperion emits (if enabled) - /// @param image The current image - /// - void setImage(const Image &image); - - /// - /// @brief Process and push new log messages from logger (if enabled) - /// - void incommingLogMessage(const Logger::T_LOG_MESSAGE &); private slots: /// /// @brief Handle emits from API of a new Token request. - /// @param id The id of the request + /// @param identifier The identifier of the request /// @param comment The comment which needs to be accepted /// - void newPendingTokenRequest(const QString &id, const QString &comment); + void issueNewPendingTokenRequest(const QString &identifier, const QString &comment); /// /// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance we are allowed to send response. /// @param success If true the request was accepted else false and no token was created /// @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 identifier The identifier 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, const int &tan); + void handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &identifier, const int &tan); /// /// @brief Handle whenever the state of a instance (HyperionIManager) changes according to enum instanceState @@ -89,11 +78,6 @@ private slots: /// void handleInstanceStateChange(InstanceState state, quint8 instance, const QString &name = QString()); - /// - /// @brief Stream a new LED Colors update - /// - void streamLedColorsUpdate(); - signals: /// /// Signal emits with the reply message provided with handleMessage() @@ -111,31 +95,8 @@ signals: void signalEvent(Event event); private: - // true if further callbacks are forbidden (http) - bool _noListener; - /// The peer address of the client - QString _peerAddress; - - // The JsonCB instance which handles data subscription/notifications - JsonCB *_jsonCB; - - // streaming buffers - QJsonObject _streaming_leds_reply; - QJsonObject _streaming_image_reply; - QJsonObject _streaming_logging_reply; - - /// flag to determine state of log streaming - bool _streaming_logging_activated; - - /// timer for led color refresh - QTimer *_ledStreamTimer; - - /// led stream connection handle - QMetaObject::Connection _ledStreamConnection; - - /// the current streaming led values - std::vector _currentLedValues; + void handleCommand(const JsonApiCommand& cmd, const QJsonObject &message); /// /// @brief Handle the switches of Hyperion instances @@ -150,14 +111,14 @@ private: /// /// @param message the incoming message /// - void handleColorCommand(const QJsonObject &message, const QString &command, int tan); + void handleColorCommand(const QJsonObject& message, const JsonApiCommand& cmd); /// /// Handle an incoming JSON Image message /// /// @param message the incoming message /// - void handleImageCommand(const QJsonObject &message, const QString &command, int tan); + void handleImageCommand(const QJsonObject &message, const JsonApiCommand& cmd); #if defined(ENABLE_EFFECTENGINE) /// @@ -165,21 +126,21 @@ private: /// /// @param message the incoming message /// - void handleEffectCommand(const QJsonObject &message, const QString &command, int tan); + void handleEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// /// Handle an incoming JSON Effect message (Write JSON Effect) /// /// @param message the incoming message /// - void handleCreateEffectCommand(const QJsonObject &message, const QString &command, int tan); + void handleCreateEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// /// Handle an incoming JSON Effect message (Delete JSON Effect) /// /// @param message the incoming message /// - void handleDeleteEffectCommand(const QJsonObject &message, const QString &command, int tan); + void handleDeleteEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd); #endif /// @@ -187,138 +148,170 @@ private: /// /// @param message the incoming message /// - void handleSysInfoCommand(const QJsonObject &message, const QString &command, int tan); + void handleSysInfoCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// /// Handle an incoming JSON Server info message /// /// @param message the incoming message /// - void handleServerInfoCommand(const QJsonObject &message, const QString &command, int tan); + void handleServerInfoCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// /// Handle an incoming JSON Clear message /// /// @param message the incoming message /// - void handleClearCommand(const QJsonObject &message, const QString &command, int tan); + void handleClearCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// /// Handle an incoming JSON Clearall message /// /// @param message the incoming message /// - void handleClearallCommand(const QJsonObject &message, const QString &command, int tan); + void handleClearallCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// /// Handle an incoming JSON Adjustment message /// /// @param message the incoming message /// - void handleAdjustmentCommand(const QJsonObject &message, const QString &command, int tan); + void handleAdjustmentCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// /// Handle an incoming JSON SourceSelect message /// /// @param message the incoming message /// - void handleSourceSelectCommand(const QJsonObject &message, const QString &command, int tan); + void handleSourceSelectCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON GetConfig message and check subcommand /// /// @param message the incoming message /// - void handleConfigCommand(const QJsonObject &message, const QString &command, int tan); + void handleConfigCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON GetSchema message from handleConfigCommand() /// /// @param message the incoming message /// - void handleSchemaGetCommand(const QJsonObject &message, const QString &command, int tan); + void handleSchemaGetCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON SetConfig message from handleConfigCommand() /// /// @param message the incoming message /// - void handleConfigSetCommand(const QJsonObject &message, const QString &command, int tan); + void handleConfigSetCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON RestoreConfig message from handleConfigCommand() /// /// @param message the incoming message /// - void handleConfigRestoreCommand(const QJsonObject &message, const QString &command, int tan); + void handleConfigRestoreCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// /// Handle an incoming JSON Component State message /// /// @param message the incoming message /// - void handleComponentStateCommand(const QJsonObject &message, const QString &command, int tan); + void handleComponentStateCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON Led Colors message /// /// @param message the incoming message /// - void handleLedColorsCommand(const QJsonObject &message, const QString &command, int tan); + void handleLedColorsCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON Logging message /// /// @param message the incoming message /// - void handleLoggingCommand(const QJsonObject &message, const QString &command, int tan); + void handleLoggingCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON Processing message /// /// @param message the incoming message /// - void handleProcessingCommand(const QJsonObject &message, const QString &command, int tan); + void handleProcessingCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON VideoMode message /// /// @param message the incoming message /// - void handleVideoModeCommand(const QJsonObject &message, const QString &command, int tan); + void handleVideoModeCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON plugin message /// /// @param message the incoming message /// - void handleAuthorizeCommand(const QJsonObject &message, const QString &command, int tan); + void handleAuthorizeCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON instance message /// /// @param message the incoming message /// - void handleInstanceCommand(const QJsonObject &message, const QString &command, int tan); + void handleInstanceCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON Led Device message /// /// @param message the incoming message /// - void handleLedDeviceCommand(const QJsonObject &message, const QString &command, int tan); + void handleLedDeviceCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON message regarding Input Sources (Grabbers) /// /// @param message the incoming message /// - void handleInputSourceCommand(const QJsonObject& message, const QString& command, int tan); + void handleInputSourceCommand(const QJsonObject& message, const JsonApiCommand& cmd); /// Handle an incoming JSON message to request remote hyperion servers providing a given hyperion service /// /// @param message the incoming message /// - void handleServiceCommand(const QJsonObject &message, const QString &command, int tan); + void handleServiceCommand(const QJsonObject &message, const JsonApiCommand& cmd); /// Handle an incoming JSON message for actions related to the overall Hyperion system /// /// @param message the incoming message /// - void handleSystemCommand(const QJsonObject &message, const QString &command, int tan); + void handleSystemCommand(const QJsonObject &message, const JsonApiCommand& cmd); + + + void applyColorAdjustments(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment); + void applyColorAdjustment(const QString &colorName, const QJsonObject &adjustment, RgbChannelAdjustment &rgbAdjustment); + void applyGammaTransform(const QString &transformName, const QJsonObject &adjustment, RgbTransform &rgbTransform, char channel); + + void applyTransforms(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment); + template + void applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(bool)); + template + void applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(double)); + template + void applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(uint8_t)); + + void handleTokenRequired(const JsonApiCommand& cmd); + void handleAdminRequired(const JsonApiCommand& cmd); + void handleNewPasswordRequired(const JsonApiCommand& cmd); + void handleLogout(const JsonApiCommand& cmd); + void handleNewPassword(const QJsonObject &message, const JsonApiCommand& cmd); + void handleCreateToken(const QJsonObject &message, const JsonApiCommand& cmd); + void handleRenameToken(const QJsonObject &message, const JsonApiCommand& cmd); + void handleDeleteToken(const QJsonObject &message, const JsonApiCommand& cmd); + void handleRequestToken(const QJsonObject &message, const JsonApiCommand& cmd); + void handleGetPendingTokenRequests(const JsonApiCommand& cmd); + void handleAnswerRequest(const QJsonObject &message, const JsonApiCommand& cmd); + void handleGetTokenList(const JsonApiCommand& cmd); + void handleLogin(const QJsonObject &message, const JsonApiCommand& cmd); + + void handleLedDeviceDiscover(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd); + void handleLedDeviceGetProperties(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd); + void handleLedDeviceIdentify(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd); + void handleLedDeviceAddAuthorization(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd); /// - /// Handle an incoming JSON message of unknown type + /// Send a standard reply indicating success /// - void handleNotImplemented(const QString &command, int tan); + void sendSuccessReply(const JsonApiCommand& cmd); /// /// Send a standard reply indicating success @@ -328,17 +321,76 @@ private: /// /// Send a standard reply indicating success with data /// - void sendSuccessDataReply(const QJsonDocument &doc, const QString &command = "", int tan = 0); + void sendSuccessDataReply(const QJsonValue &infoData, const JsonApiCommand& cmd); + + /// + /// Send a standard reply indicating success with data + /// + void sendSuccessDataReply(const QJsonValue &infoData, const QString &command = "", int tan = 0); + + /// + /// Send a standard reply indicating success with data and error details + /// + void sendSuccessDataReplyWithError(const QJsonValue &infoData, const JsonApiCommand& cmd, const QStringList& errorDetails = {}); + + /// + /// Send a standard reply indicating success with data and error details + /// + void sendSuccessDataReplyWithError(const QJsonValue &infoData, const QString &command = "", int tan = 0, const QStringList& errorDetails = {}); + + + /// + /// Send a message with data. + /// Note: To be used as a new message and not as a response to a previous request. + /// + void sendNewRequest(const QJsonValue &infoData, const JsonApiCommand& cmd); + + /// + /// Send a message with data + /// Note: To be used as a new message and not as a response to a previous request. + /// + void sendNewRequest(const QJsonValue &infoData, const QString &command); /// /// Send an error message back to the client /// /// @param error String describing the error /// - void sendErrorReply(const QString &error, const QString &command = "", int tan = 0); + void sendErrorReply(const QString &error, const JsonApiCommand& cmd); + + /// + /// Send an error message back to the client + /// + /// @param error String describing the error + /// @param errorDetails additional information detailing the error scenario + /// + void sendErrorReply(const QString &error, const QStringList& errorDetails, const JsonApiCommand& cmd); + + /// + /// Send an error message back to the client + /// + /// @param error String describing the error + /// @param errorDetails additional information detailing the error scenario + /// + void sendErrorReply(const QString &error, const QStringList& errorDetails = {}, const QString &command = "", int tan = 0); + + void sendNoAuthorization(const JsonApiCommand& cmd); /// /// @brief Kill all signal/slot connections to stop possible data emitter /// - void stopDataConnections(); + void stopDataConnections() override; + + static QString findCommand (const QString& jsonS); + static int findTan (const QString& jsonString); + + // true if further callbacks are forbidden (http) + bool _noListener; + + /// The peer address of the client + QString _peerAddress; + + // The JsonCallbacks instance which handles data subscription/notifications + QSharedPointer _jsonCB; + }; diff --git a/include/api/JsonApiCommand.h b/include/api/JsonApiCommand.h new file mode 100644 index 00000000..5ec8caf8 --- /dev/null +++ b/include/api/JsonApiCommand.h @@ -0,0 +1,320 @@ +#ifndef JSONAPICOMMAND_H +#define JSONAPICOMMAND_H + +#include +#include +#include + +class Command { +public: + enum Type { + Unknown, + Adjustment, + Authorize, + Clear, + ClearAll, + Color, + ComponentState, + Config, + Correction, + CreateEffect, + DeleteEffect, + Effect, + Image, + InputSource, + Instance, + LedColors, + LedDevice, + Logging, + Processing, + ServerInfo, + Service, + SourceSelect, + SysInfo, + System, + Temperature, + Transform, + VideoMode + }; + + static QString toString(Type type) { + switch (type) { + case Adjustment: return "adjustment"; + case Authorize: return "authorize"; + case Clear: return "clear"; + case ClearAll: return "clearall"; + case Color: return "color"; + case ComponentState: return "componentstate"; + case Config: return "config"; + case Correction: return "correction"; + case CreateEffect: return "create-effect"; + case DeleteEffect: return "delete-effect"; + case Effect: return "effect"; + case Image: return "image"; + case InputSource: return "inputsource"; + case Instance: return "instance"; + case LedColors: return "ledcolors"; + case LedDevice: return "leddevice"; + case Logging: return "logging"; + case Processing: return "processing"; + case ServerInfo: return "serverinfo"; + case SourceSelect: return "sourceselect"; + case SysInfo: return "sysinfo"; + case System: return "system"; + case Temperature: return "temperature"; + case Transform: return "transform"; + case VideoMode: return "videomode"; + case Service: return "service"; + default: return "unknown"; + } + } +}; + +class SubCommand { +public: + enum Type { + Unknown, + Empty, + AdminRequired, + AddAuthorization, + AnswerRequest, + CreateInstance, + CreateToken, + DeleteInstance, + DeleteToken, + Discover, + GetConfig, + GetInfo, + GetPendingTokenRequests, + GetProperties, + GetSchema, + GetSubscriptionCommands, + GetSubscriptions, + GetTokenList, + Identify, + Idle, + ImageStreamStart, + ImageStreamStop, + LedStreamStart, + LedStreamStop, + Login, + Logout, + NewPassword, + NewPasswordRequired, + Reload, + RenameToken, + RequestToken, + Restart, + RestoreConfig, + Resume, + SaveName, + SetConfig, + Start, + StartInstance, + Stop, + StopInstance, + Subscribe, + Suspend, + SwitchTo, + ToggleIdle, + ToggleSuspend, + TokenRequired, + Unsubscribe, + Update + }; + + static QString toString(Type type) { + switch (type) { + case Empty: return ""; + case AdminRequired: return "adminRequired"; + case AddAuthorization: return "addAuthorization"; + case AnswerRequest: return "answerRequest"; + case CreateInstance: return "createInstance"; + case CreateToken: return "createToken"; + case DeleteInstance: return "deleteInstance"; + case DeleteToken: return "deleteToken"; + case Discover: return "discover"; + case GetConfig: return "getconfig"; + case GetInfo: return "getInfo"; + case GetPendingTokenRequests: return "getPendingTokenRequests"; + case GetProperties: return "getProperties"; + case GetSchema: return "getschema"; + case GetSubscriptionCommands: return "getSubscriptionCommands"; + case GetSubscriptions: return "getSubscriptions"; + case GetTokenList: return "getTokenList"; + case Identify: return "identify"; + case Idle: return "idle"; + case ImageStreamStart: return "imagestream-start"; + case ImageStreamStop: return "imagestream-stop"; + case LedStreamStart: return "ledstream-start"; + case LedStreamStop: return "ledstream-stop"; + case Login: return "login"; + case Logout: return "logout"; + case NewPassword: return "newPassword"; + case NewPasswordRequired: return "newPasswordRequired"; + case Reload: return "reload"; + case RenameToken: return "renameToken"; + case RequestToken: return "requestToken"; + case Restart: return "restart"; + case RestoreConfig: return "restoreconfig"; + case Resume: return "resume"; + case SaveName: return "saveName"; + case SetConfig: return "setconfig"; + case Start: return "start"; + case StartInstance: return "startInstance"; + case Stop: return "stop"; + case StopInstance: return "stopInstance"; + case Subscribe: return "subscribe"; + case Suspend: return "suspend"; + case SwitchTo: return "switchTo"; + case ToggleIdle: return "toggleIdle"; + case ToggleSuspend: return "toggleSuspend"; + case TokenRequired: return "tokenRequired"; + case Unsubscribe: return "unsubscribe"; + case Update: return "update"; + default: return "unknown"; + } + } +}; + +class Authorization { +public: + enum Type { + Admin, + Yes, + No + }; +}; + +class NoListenerCmd { +public: + enum Type { + No, + Yes + }; +}; + +class JsonApiCommand { +public: + + JsonApiCommand() + : command(Command::Unknown), + subCommand(SubCommand::Unknown), + tan(0), + authorization(Authorization::Admin), + isNolistenerCmd(NoListenerCmd::Yes) + {} + + JsonApiCommand(Command::Type command, SubCommand::Type subCommand, + Authorization::Type authorization, + NoListenerCmd::Type isNolistenerCmd, int tan = 0) + : command(command), + subCommand(subCommand), + tan(tan), + authorization(authorization), + isNolistenerCmd(isNolistenerCmd) + {} + + Command::Type getCommand() const { return command; } + SubCommand::Type getSubCommand() const { return subCommand; } + int getTan() const { return tan; } + + QString toString() const { + QString cmd = Command::toString(command); + if (subCommand > SubCommand::Empty) { + cmd += QString("-%2").arg(SubCommand::toString(subCommand)); + } + return cmd; + } + + Command::Type command; + SubCommand::Type subCommand; + int tan; + + Authorization::Type authorization; + NoListenerCmd::Type isNolistenerCmd; +}; + +typedef QMap, JsonApiCommand> CommandLookupMap; + +class ApiCommandRegister { +public: + + static const CommandLookupMap& getCommandLookup() { + static const CommandLookupMap commandLookup { + { {"adjustment", ""}, { Command::Adjustment, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"authorize", "adminRequired"}, { Command::Authorize, SubCommand::AdminRequired, Authorization::No, NoListenerCmd::Yes} }, + { {"authorize", "answerRequest"}, { Command::Authorize, SubCommand::AnswerRequest, Authorization::Admin, NoListenerCmd::No} }, + { {"authorize", "createToken"}, { Command::Authorize, SubCommand::CreateToken, Authorization::Admin, NoListenerCmd::No} }, + { {"authorize", "deleteToken"}, { Command::Authorize, SubCommand::DeleteToken, Authorization::Admin, NoListenerCmd::Yes} }, + { {"authorize", "getPendingTokenRequests"}, { Command::Authorize, SubCommand::GetPendingTokenRequests, Authorization::Admin, NoListenerCmd::No} }, + { {"authorize", "getTokenList"}, { Command::Authorize, SubCommand::GetTokenList, Authorization::Admin, NoListenerCmd::Yes} }, + { {"authorize", "login"}, { Command::Authorize, SubCommand::Login, Authorization::No, NoListenerCmd::No} }, + { {"authorize", "logout"}, { Command::Authorize, SubCommand::Logout, Authorization::No, NoListenerCmd::No} }, + { {"authorize", "newPassword"}, { Command::Authorize, SubCommand::NewPassword, Authorization::Admin, NoListenerCmd::Yes} }, + { {"authorize", "newPasswordRequired"}, { Command::Authorize, SubCommand::NewPasswordRequired, Authorization::No, NoListenerCmd::Yes} }, + { {"authorize", "renameToken"}, { Command::Authorize, SubCommand::RenameToken, Authorization::Admin, NoListenerCmd::Yes} }, + { {"authorize", "requestToken"}, { Command::Authorize, SubCommand::RequestToken, Authorization::No, NoListenerCmd::Yes} }, + { {"authorize", "tokenRequired"}, { Command::Authorize, SubCommand::TokenRequired, Authorization::No, NoListenerCmd::Yes} }, + { {"clear", ""}, { Command::Clear, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"clearall", ""}, { Command::ClearAll, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"color", ""}, { Command::Color, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"componentstate", ""}, { Command::ComponentState, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"config", "getconfig"}, { Command::Config, SubCommand::GetConfig, Authorization::Admin, NoListenerCmd::Yes} }, + { {"config", "getschema"}, { Command::Config, SubCommand::GetSchema, Authorization::Admin, NoListenerCmd::Yes} }, + { {"config", "reload"}, { Command::Config, SubCommand::Reload, Authorization::Admin, NoListenerCmd::Yes} }, + { {"config", "restoreconfig"}, { Command::Config, SubCommand::RestoreConfig, Authorization::Admin, NoListenerCmd::Yes} }, + { {"config", "setconfig"}, { Command::Config, SubCommand::SetConfig, Authorization::Admin, NoListenerCmd::Yes} }, + { {"correction", ""}, { Command::Correction, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"create-effect", ""}, { Command::CreateEffect, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"delete-effect", ""}, { Command::DeleteEffect, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"effect", ""}, { Command::Effect, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"image", ""}, { Command::Image, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"inputsource", "discover"}, { Command::InputSource, SubCommand::Discover, Authorization::Yes, NoListenerCmd::Yes} }, + { {"inputsource", "getProperties"}, { Command::InputSource, SubCommand::GetProperties, Authorization::Yes, NoListenerCmd::Yes} }, + { {"instance", "createInstance"}, { Command::Instance, SubCommand::CreateInstance, Authorization::Admin, NoListenerCmd::Yes} }, + { {"instance", "deleteInstance"}, { Command::Instance, SubCommand::DeleteInstance, Authorization::Admin, NoListenerCmd::Yes} }, + { {"instance", "saveName"}, { Command::Instance, SubCommand::SaveName, Authorization::Admin, NoListenerCmd::Yes} }, + { {"instance", "startInstance"}, { Command::Instance, SubCommand::StartInstance, Authorization::Yes, NoListenerCmd::Yes} }, + { {"instance", "stopInstance"}, { Command::Instance, SubCommand::StopInstance, Authorization::Yes, NoListenerCmd::Yes} }, + { {"instance", "switchTo"}, { Command::Instance, SubCommand::SwitchTo, Authorization::Yes, NoListenerCmd::Yes} }, + { {"ledcolors", "imagestream-start"}, { Command::LedColors, SubCommand::ImageStreamStart, Authorization::Yes, NoListenerCmd::Yes} }, + { {"ledcolors", "imagestream-stop"}, { Command::LedColors, SubCommand::ImageStreamStop, Authorization::Yes, NoListenerCmd::Yes} }, + { {"ledcolors", "ledstream-start"}, { Command::LedColors, SubCommand::LedStreamStart, Authorization::Yes, NoListenerCmd::Yes} }, + { {"ledcolors", "ledstream-stop"}, { Command::LedColors, SubCommand::LedStreamStop, Authorization::Yes, NoListenerCmd::Yes} }, + { {"leddevice", "addAuthorization"}, { Command::LedDevice, SubCommand::AddAuthorization, Authorization::Yes, NoListenerCmd::Yes} }, + { {"leddevice", "discover"}, { Command::LedDevice, SubCommand::Discover, Authorization::Yes, NoListenerCmd::Yes} }, + { {"leddevice", "getProperties"}, { Command::LedDevice, SubCommand::GetProperties, Authorization::Yes, NoListenerCmd::Yes} }, + { {"leddevice", "identify"}, { Command::LedDevice, SubCommand::Identify, Authorization::Yes, NoListenerCmd::Yes} }, + { {"logging", "start"}, { Command::Logging, SubCommand::Start, Authorization::Yes, NoListenerCmd::Yes} }, + { {"logging", "stop"}, { Command::Logging, SubCommand::Stop, Authorization::Yes, NoListenerCmd::Yes} }, + { {"logging", "update"}, { Command::Logging, SubCommand::Update, Authorization::Yes, NoListenerCmd::Yes} }, + { {"processing", ""}, { Command::Processing, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"sourceselect", ""}, { Command::SourceSelect, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"serverinfo", ""}, { Command::ServerInfo, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"serverinfo", "getInfo"}, { Command::ServerInfo, SubCommand::GetInfo, Authorization::Yes, NoListenerCmd::Yes} }, + { {"serverinfo", "subscribe"}, { Command::ServerInfo, SubCommand::Subscribe, Authorization::Yes, NoListenerCmd::No} }, + { {"serverinfo", "unsubscribe"}, { Command::ServerInfo, SubCommand::Unsubscribe, Authorization::Yes, NoListenerCmd::No} }, + { {"serverinfo", "getSubscriptions"}, { Command::ServerInfo, SubCommand::GetSubscriptions, Authorization::Yes, NoListenerCmd::No} }, + { {"serverinfo", "getSubscriptionCommands"}, { Command::ServerInfo, SubCommand::GetSubscriptionCommands, Authorization::No, NoListenerCmd::No} }, + { {"sysinfo", ""}, { Command::SysInfo, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"system", "restart"}, { Command::System, SubCommand::Restart, Authorization::Yes, NoListenerCmd::Yes} }, + { {"system", "resume"}, { Command::System, SubCommand::Resume, Authorization::Yes, NoListenerCmd::Yes} }, + { {"system", "suspend"}, { Command::System, SubCommand::Suspend, Authorization::Yes, NoListenerCmd::Yes} }, + { {"system", "toggleSuspend"}, { Command::System, SubCommand::ToggleSuspend, Authorization::Yes, NoListenerCmd::Yes} }, + { {"system", "idle"}, { Command::System, SubCommand::Idle, Authorization::Yes, NoListenerCmd::Yes} }, + { {"system", "toggleIdle"}, { Command::System, SubCommand::ToggleIdle, Authorization::Yes, NoListenerCmd::Yes} }, + { {"temperature", ""}, { Command::Temperature, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"transform", ""}, { Command::Transform, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"videomode", ""}, { Command::VideoMode, SubCommand::Empty, Authorization::Yes, NoListenerCmd::Yes} }, + { {"service", "discover"}, { Command::Service, SubCommand::Discover, Authorization::Yes, NoListenerCmd::Yes} } + }; + return commandLookup; + } + + static JsonApiCommand getCommandInfo(const QString& command, const QString& subCommand) { + return getCommandLookup().value({command, subCommand}); + } +}; + +#endif // JSONAPICOMMAND_H diff --git a/include/api/JsonApiSubscription.h b/include/api/JsonApiSubscription.h new file mode 100644 index 00000000..f924099f --- /dev/null +++ b/include/api/JsonApiSubscription.h @@ -0,0 +1,108 @@ +#ifndef JSONAPISUBSCRIPTION_H +#define JSONAPISUBSCRIPTION_H + +#include // Required to determine the cmake options + +#include +#include + + +class Subscription { +public: + enum Type { + Unknown, + AdjustmentUpdate, + ComponentsUpdate, +#if defined(ENABLE_EFFECTENGINE) + EffectsUpdate, +#endif + ImageToLedMappingUpdate, + ImageUpdate, + InstanceUpdate, + LedColorsUpdate, + LedsUpdate, + LogMsgUpdate, + PrioritiesUpdate, + SettingsUpdate, + TokenUpdate, + VideomodeUpdate + }; + + static QString toString(Type type) { + switch (type) { + case AdjustmentUpdate: return "adjustment-update"; + case ComponentsUpdate: return "components-update"; +#if defined(ENABLE_EFFECTENGINE) + case EffectsUpdate: return "effects-update"; +#endif + case ImageToLedMappingUpdate: return "imageToLedMapping-update"; + case ImageUpdate: return "image-update"; + case InstanceUpdate: return "instance-update"; + case LedColorsUpdate: return "led-colors-update"; + case LedsUpdate: return "leds-update"; + case LogMsgUpdate: return "logmsg-update"; + case PrioritiesUpdate: return "priorities-update"; + case SettingsUpdate: return "settings-update"; + case TokenUpdate: return "token-update"; + case VideomodeUpdate: return "videomode-update"; + default: return "unknown"; + } + } +}; + +class JsonApiSubscription { +public: + + JsonApiSubscription() + : cmd(Subscription::Unknown), + isAll(false) + {} + + JsonApiSubscription(Subscription::Type cmd, bool isAll) + : cmd(cmd), + isAll(isAll) + {} + + Subscription::Type getSubscription() const { return cmd; } + bool isPartOfAll() const { return isAll; } + + QString toString() const { + return Subscription::toString(cmd); + } + + Subscription::Type cmd; + bool isAll; +}; + +typedef QMap SubscriptionLookupMap; + +class ApiSubscriptionRegister { +public: + + static const SubscriptionLookupMap& getSubscriptionLookup() { + static const SubscriptionLookupMap subscriptionLookup { + { {"adjustment-update"}, { Subscription::AdjustmentUpdate, true} }, + { {"components-update"}, { Subscription::ComponentsUpdate, true} }, +#if defined(ENABLE_EFFECTENGINE) + { {"effects-update"}, { Subscription::EffectsUpdate, true} }, +#endif + { {"imageToLedMapping-update"}, { Subscription::ImageToLedMappingUpdate, true} }, + { {"image-update"}, { Subscription::ImageUpdate, false} }, + { {"instance-update"}, { Subscription::InstanceUpdate, true} }, + { {"led-colors-update"}, { Subscription::LedColorsUpdate, true} }, + { {"leds-update"}, { Subscription::LedsUpdate, false} }, + { {"logmsg-update"}, { Subscription::LogMsgUpdate, false} }, + { {"priorities-update"}, { Subscription::PrioritiesUpdate, true} }, + { {"settings-update"}, { Subscription::SettingsUpdate, true} }, + { {"token-update"}, { Subscription::TokenUpdate, true} }, + { {"videomode-update"}, { Subscription::VideomodeUpdate, true} } + }; + return subscriptionLookup; + } + + static JsonApiSubscription getSubscriptionInfo(const QString& subscription) { + return getSubscriptionLookup().value({subscription}); + } +}; + +#endif // JSONAPISUBSCRIPTION_H diff --git a/include/api/JsonCB.h b/include/api/JsonCallbacks.h similarity index 56% rename from include/api/JsonCB.h rename to include/api/JsonCallbacks.h index 2d59b3eb..88bc9689 100644 --- a/include/api/JsonCB.h +++ b/include/api/JsonCallbacks.h @@ -1,8 +1,12 @@ #pragma once +#include "api/JsonApiSubscription.h" +#include + // qt incl #include #include +#include // components def #include @@ -20,32 +24,64 @@ class Hyperion; class ComponentRegister; class PriorityMuxer; -class JsonCB : public QObject +class JsonCallbacks : public QObject { Q_OBJECT public: - JsonCB(QObject* parent); + JsonCallbacks(Logger* log, const QString& peerAddress, QObject* parent); /// /// @brief Subscribe to future data updates given by cmd - /// @param cmd The cmd which will be subscribed for - /// @param unsubscribe Revert subscription + /// @param cmd The cmd which will be subscribed for /// @return True on success, false if not found /// - bool subscribeFor(const QString& cmd, bool unsubscribe = false); + bool subscribe(const QString& cmd); + + /// + /// @brief Subscribe to future data updates given by subscription list + /// @param type Array of subscriptionsm + /// + QStringList subscribe(const QJsonArray& subscriptions); + + /// + /// @brief Subscribe to future data updates given by cmd + /// @param cmd The cmd which will be subscribed to + /// @return True on success, false if not found + /// + bool subscribe(Subscription::Type subscription); + + /// + /// @brief Unsubscribe to future data updates given by cmd + /// @param cmd The cmd which will be unsubscribed + /// @return True on success, false if not found + /// + bool unsubscribe(const QString& cmd); + + /// + /// @brief Unsubscribe to future data updates given by subscription list + /// @param type Array of subscriptions + /// + QStringList unsubscribe(const QJsonArray& subscriptions); + + /// + /// @brief Unsubscribe to future data updates given by cmd + /// @param cmd The cmd which will be subscribed to + /// @return True on success, false if not found + /// + bool unsubscribe(Subscription::Type cmd); /// /// @brief Get all possible commands to subscribe for + /// @param fullList Return all possible commands or those not triggered by API requests (subscriptions="ALL") /// @return The list of commands /// - QStringList getCommands() { return _availableCommands; }; - + QStringList getCommands(bool fullList = true) const; /// /// @brief Get all subscribed commands /// @return The list of commands /// - QStringList getSubscribedCommands() { return _subscribedCommands; }; + QStringList getSubscribedCommands() const; /// /// @brief Reset subscriptions, disconnect all signals @@ -124,18 +160,42 @@ private slots: /// void handleTokenChange(const QVector &def); + /// + /// @brief Is called whenever the current Hyperion instance pushes new led raw values (if enabled) + /// @param ledColors The current led colors + /// + void handleLedColorUpdate(const std::vector &ledColors); + + /// + /// @brief Is called whenever the current Hyperion instance pushes new image update (if enabled) + /// @param image The current image + /// + void handleImageUpdate(const Image &image); + + /// + /// @brief Process and push new log messages from logger (if enabled) + /// + void handleLogMessageUpdate(const Logger::T_LOG_MESSAGE &); + private: - /// pointer of Hyperion instance + + /// construct callback msg + void doCallback(Subscription::Type cmd, const QVariant& data); + + Logger *_log; Hyperion* _hyperion; + + /// The peer address of the client + QString _peerAddress; + /// pointer of comp register ComponentRegister* _componentRegister; /// priority muxer instance PriorityMuxer* _prioMuxer; - /// contains all available commands - QStringList _availableCommands; /// contains active subscriptions - QStringList _subscribedCommands; - /// construct callback msg - void doCallback(const QString& cmd, const QVariant& data); + QSet _subscribedCommands; + + /// flag to determine state of log streaming + bool _islogMsgStreamingActive; }; diff --git a/include/api/JsonInfo.h b/include/api/JsonInfo.h new file mode 100644 index 00000000..1346f97e --- /dev/null +++ b/include/api/JsonInfo.h @@ -0,0 +1,43 @@ +#ifndef JSONINFO_H +#define JSONINFO_H + +#include +#include +#include + +#include +#include + +class JsonInfo +{ + +public: + static QJsonArray getAdjustmentInfo(const Hyperion* hyperion, Logger* log); + static QJsonArray getPrioritiestInfo(const Hyperion* hyperion); + static QJsonArray getPrioritiestInfo(int currentPriority, const PriorityMuxer::InputsMap& activeInputs); + static QJsonArray getEffects(const Hyperion* hyperion); + static QJsonArray getAvailableScreenGrabbers(); + static QJsonArray getAvailableVideoGrabbers(); + static QJsonArray getAvailableAudioGrabbers(); + static QJsonObject getGrabbers(const Hyperion* hyperion); + static QJsonObject getAvailableLedDevices(); + static QJsonObject getCecInfo(); + static QJsonArray getServices(); + static QJsonArray getComponents(const Hyperion* hyperion); + static QJsonArray getInstanceInfo(); + static QJsonArray getActiveEffects(const Hyperion* hyperion); + static QJsonArray getActiveColors(const Hyperion* hyperion); + static QJsonArray getTransformationInfo(const Hyperion* hyperion); + static QJsonObject getSystemInfo(const Hyperion* hyperion); + QJsonObject discoverSources (const QString& sourceType, const QJsonObject& params); + +private: + + template + void discoverGrabber(QJsonArray& inputs, const QJsonObject& params) const; + QJsonArray discoverScreenInputs(const QJsonObject& params) const; + QJsonArray discoverVideoInputs(const QJsonObject& params) const; + QJsonArray discoverAudioInputs(const QJsonObject& params) const; +}; + +#endif // JSONINFO_H diff --git a/include/api/apiStructs.h b/include/api/apiStructs.h index 112132fc..de1d6e67 100644 --- a/include/api/apiStructs.h +++ b/include/api/apiStructs.h @@ -2,6 +2,9 @@ #include #include +#include + +#include struct ImageCmdData { diff --git a/include/grabber/GrabberConfig.h b/include/grabber/GrabberConfig.h new file mode 100644 index 00000000..f3a575c2 --- /dev/null +++ b/include/grabber/GrabberConfig.h @@ -0,0 +1,58 @@ +#ifndef GRABBERCONFIG_H +#define GRABBERCONFIG_H + +#if defined(ENABLE_MF) +#include +#elif defined(ENABLE_V4L2) +#include +#endif + +#if defined(ENABLE_AUDIO) +#include + +#ifdef WIN32 +#include +#endif + +#ifdef __linux__ +#include +#endif +#endif + +#ifdef ENABLE_QT +#include +#endif + +#ifdef ENABLE_DX +#include +#endif + +#if defined(ENABLE_X11) +#include +#endif + +#if defined(ENABLE_XCB) +#include +#endif + +#if defined(ENABLE_DX) +#include +#endif + +#if defined(ENABLE_FB) +#include +#endif + +#if defined(ENABLE_DISPMANX) +#include +#endif + +#if defined(ENABLE_AMLOGIC) +#include +#endif + +#if defined(ENABLE_OSX) +#include +#endif + +#endif // GRABBERCONFIG_H diff --git a/include/hyperion/AuthManager.h b/include/hyperion/AuthManager.h index a7750552..790f70b6 100644 --- a/include/hyperion/AuthManager.h +++ b/include/hyperion/AuthManager.h @@ -43,24 +43,12 @@ public: /// QString getID() const { return _uuid; } - /// - /// @brief Check authorization is required according to the user setting - /// @return True if authorization required else false - /// - bool isAuthRequired() const { return _authRequired; } - /// /// @brief Check if authorization is required for local network connections /// @return True if authorization required else false /// bool isLocalAuthRequired() const { return _localAuthRequired; } - /// - /// @brief Check if authorization is required for local network connections for admin access - /// @return True if authorization required else false - /// - bool isLocalAdminAuthRequired() const { return _localAdminAuthRequired; } - /// /// @brief Reset Hyperion user /// @return True on success else false @@ -232,15 +220,9 @@ private: /// All pending requests QMap _pendingRequests; - /// Reflect state of global auth - bool _authRequired; - /// Reflect state of local auth bool _localAuthRequired; - /// Reflect state of local admin auth - bool _localAdminAuthRequired; - /// Timer for counting against pendingRequest timeouts QTimer *_timer; diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 3a442f40..f9b34e33 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -67,6 +67,7 @@ class Hyperion : public QObject Q_OBJECT public: /// Type definition of the info structure used by the priority muxer + using InputsMap = PriorityMuxer::InputsMap; using InputInfo = PriorityMuxer::InputInfo; /// @@ -107,7 +108,7 @@ public: /// QString getActiveDeviceType() const; - bool getReadOnlyMode() {return _readOnlyMode; } + bool getReadOnlyMode() const {return _readOnlyMode; } public slots: @@ -235,13 +236,13 @@ public slots: /// @param priority The priority channel of the effect /// @param timeout The timeout of the effect (after the timout, the effect will be cleared) int setEffect(const QString &effectName - , const QJsonObject &args - , int priority - , int timeout = PriorityMuxer::ENDLESS - , const QString &pythonScript = "" - , const QString &origin="System" - , const QString &imageData = "" - ); + , const QJsonObject &args + , int priority + , int timeout = PriorityMuxer::ENDLESS + , const QString &pythonScript = "" + , const QString &origin="System" + , const QString &imageData = "" + ); /// Get the list of available effects /// @return The list of available effects @@ -303,7 +304,14 @@ public slots: QList getActivePriorities() const; /// - /// Returns the information of a specific priorrity channel + /// Returns the information of all priority channels. + /// + /// @return The information fo all priority channels + /// + PriorityMuxer::InputsMap getPriorityInfo() const; + + /// + /// Returns the information of a specific priority channel /// /// @param[in] priority The priority channel /// @@ -346,7 +354,7 @@ public slots: /// @brief Get the component Register /// return Component register pointer /// - ComponentRegister* getComponentRegister() { return _componentRegister; } + ComponentRegister* getComponentRegister() const { return _componentRegister; } /// /// @brief Called from components to update their current state. DO NOT CALL FROM USERS diff --git a/include/hyperion/PriorityMuxer.h b/include/hyperion/PriorityMuxer.h index ed8d4fb5..d307843b 100644 --- a/include/hyperion/PriorityMuxer.h +++ b/include/hyperion/PriorityMuxer.h @@ -141,6 +141,13 @@ public: /// QList getPriorities() const; + /// + /// Returns the information of all priority channels. + /// + /// @return The information fo all priority channels + /// + InputsMap getInputInfo() const; + /// /// Returns the information of a specified priority channel. /// If a priority is no longer available the _lowestPriorityInfo (255) is returned diff --git a/include/utils/JsonUtils.h b/include/utils/JsonUtils.h index 694e279f..10b89d90 100644 --- a/include/utils/JsonUtils.h +++ b/include/utils/JsonUtils.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include namespace JsonUtils { @@ -14,7 +16,7 @@ namespace JsonUtils { /// @param[in] ignError Ignore errors during file read (no log output) /// @return true on success else false /// - bool readFile(const QString& path, QJsonObject& obj, Logger* log, bool ignError=false); + QPair readFile(const QString& path, QJsonObject& obj, Logger* log, bool ignError=false); /// /// @brief read a schema file and resolve $refs @@ -33,7 +35,7 @@ namespace JsonUtils { /// @param[in] log The logger of the caller to print errors /// @return true on success else false /// - bool parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log); + QPair parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log); /// /// @brief parse a json QString and get a QJsonArray. Overloaded function @@ -42,8 +44,8 @@ namespace JsonUtils { /// @param[out] arr Retuns the parsed QJsonArray /// @param[in] log The logger of the caller to print errors /// @return true on success else false - /// - bool parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log); + // + QPair parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log); /// /// @brief parse a json QString and get a QJsonDocument @@ -53,7 +55,7 @@ namespace JsonUtils { /// @param[in] log The logger of the caller to print errors /// @return true on success else false /// - bool parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log); + QPair parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log); /// /// @brief Validate json data against a schema @@ -63,7 +65,7 @@ namespace JsonUtils { /// @param[in] log The logger of the caller to print errors /// @return true on success else false /// - bool validate(const QString& file, const QJsonObject& json, const QString& schemaPath, Logger* log); + QPair validate(const QString& file, const QJsonObject& json, const QString& schemaPath, Logger* log); /// /// @brief Validate json data against a schema @@ -73,7 +75,7 @@ namespace JsonUtils { /// @param[in] log The logger of the caller to print errors /// @return true on success else false /// - bool validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log); + QPair validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log); /// /// @brief Write json data to file diff --git a/include/utils/NetOrigin.h b/include/utils/NetOrigin.h index 75796e66..648d3de3 100644 --- a/include/utils/NetOrigin.h +++ b/include/utils/NetOrigin.h @@ -47,7 +47,9 @@ private slots: private: Logger* _log; /// True when internet access is allowed - bool _internetAccessAllowed; + bool _isInternetAccessAllowed; + /// True when internet access is restricted by a white list + bool _isInternetAccessRestricted; /// Whitelisted ip addresses QList _ipWhitelist; diff --git a/include/utils/jsonschema/QJsonFactory.h b/include/utils/jsonschema/QJsonFactory.h index 2fcf5882..1a9190cd 100644 --- a/include/utils/jsonschema/QJsonFactory.h +++ b/include/utils/jsonschema/QJsonFactory.h @@ -31,7 +31,9 @@ public: if (!schemaChecker.validate(configTree).first) { for (int i = 0; i < messages.size(); ++i) + { std::cout << messages[i].toStdString() << std::endl; + } std::cerr << "Validation failed for configuration file: " << config.toStdString() << std::endl; return -3; @@ -61,9 +63,10 @@ public: if (error.error != QJsonParseError::NoError) { // report to the user the failure and their locations in the document. - int errorLine(0), errorColumn(0); + int errorLine(0); + int errorColumn(0); - for( int i=0, count=qMin( error.offset,config.size()); i // stl includes -#include -#include // Qt includes #include @@ -27,92 +25,91 @@ // ledmapping int <> string transform methods #include -// api includes -#include - using namespace hyperion; +// Constants +namespace { + +const int IMAGE_HEIGHT_MAX = 2000; +const int IMAGE_WIDTH_MAX = 2000; +const int IMAGE_SCALE = 2000; +} + API::API(Logger *log, bool localConnection, QObject *parent) - : QObject(parent) + : QObject(parent) { qRegisterMetaType("int64_t"); qRegisterMetaType("VideoMode"); qRegisterMetaType>("std::map"); - // Init - _log = log; - _authManager = AuthManager::getInstance(); + // Init + _log = log; + _authManager = AuthManager::getInstance(); _instanceManager = HyperionIManager::getInstance(); - _localConnection = localConnection; + _localConnection = localConnection; - _authorized = false; - _adminAuthorized = false; + _authorized = false; + _adminAuthorized = false; - _currInstanceIndex = 0; + _currInstanceIndex = 0; - // connect to possible token responses that has been requested - 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 token responses that has been requested + connect(_authManager, &AuthManager::tokenResponse, this, [=] (bool success, const QObject *caller, const QString &token, const QString &comment, const QString &tokenId, const int &tan) + { + if (this == caller) + { + emit onTokenResponse(success, token, comment, tokenId, 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); - }); + connect(_instanceManager, &HyperionIManager::startInstanceResponse, this, [=] (const QObject *caller, const int &tan) + { + if (this == caller) + { + emit onStartInstanceResponse(tan); + } + }); } void API::init() { _hyperion = _instanceManager->getHyperionInstance(0); + _authorized = false; - bool apiAuthRequired = _authManager->isAuthRequired(); - qDebug() << "API::init - apiAuthRequired: " << apiAuthRequired; + // For security we block external connections, if default PW is set + if (!_localConnection && API::hasHyperionDefaultPw()) + { + Warning(_log, "Non local network connect attempt identified, but default Hyperion passwort set! - Reject connection."); + emit forceClose(); + } - // For security we block external connections if default PW is set - if (!_localConnection && API::hasHyperionDefaultPw()) - { - emit forceClose(); - } - // if this is localConnection and network allows unauth locals, set authorized flag - if (!_authManager->isLocalAuthRequired() && _localConnection) + // if this is localConnection and network allows unauth locals + if ( _localConnection && !_authManager->isLocalAuthRequired()) { _authorized = true; - _adminAuthorized = !_authManager->isLocalAdminAuthRequired(); - } - else + + // // admin access is only allowed after login via user & password or via authorization via token. + _adminAuthorized = false; +} + +void API::setColor(int priority, const std::vector &ledColors, int timeout_ms, const QString &origin, hyperion::Components /*callerComp*/) +{ + if (ledColors.size() % 3 == 0) { - if (_localConnection) + std::vector fledColors; + for (unsigned i = 0; i < ledColors.size(); i += 3) { - _authorized = !_authManager->isLocalAuthRequired(); - _adminAuthorized = !_authManager->isLocalAdminAuthRequired(); - } + fledColors.emplace_back(ColorRgb{ledColors[i], ledColors[i + 1], ledColors[i + 2]}); + } + QMetaObject::invokeMethod(_hyperion, "setColor", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(std::vector, fledColors), Q_ARG(int, timeout_ms), Q_ARG(QString, origin)); } - qDebug() << "API::init - _authorized: " << _authorized; - qDebug() << "API::init - _adminAuthorized: " << _adminAuthorized; } -void API::setColor(int priority, const std::vector &ledColors, int timeout_ms, const QString &origin, hyperion::Components callerComp) +bool API::setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components /*callerComp*/) { - std::vector fledColors; - if (ledColors.size() % 3 == 0) - { - for (unsigned i = 0; i < ledColors.size(); i += 3) - { - fledColors.emplace_back(ColorRgb{ledColors[i], ledColors[i + 1], ledColors[i + 2]}); - } - QMetaObject::invokeMethod(_hyperion, "setColor", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(std::vector, fledColors), Q_ARG(int, timeout_ms), Q_ARG(QString, origin)); - } -} - -bool API::setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components callerComp) -{ - // truncate name length - data.imgName.truncate(16); + // truncate name length + data.imgName.truncate(16); if (!data.format.isEmpty()) { @@ -130,424 +127,475 @@ bool API::setImage(ImageCmdData &data, hyperion::Components comp, QString &reply } QImage img = QImage::fromData(data.data, QSTRING_CSTR(data.format)); - if (img.isNull()) - { + if (img.isNull()) + { replyMsg = "Failed to parse picture, the file might be corrupted or content does not match the given format [" + data.format + "]"; - return false; - } + return false; + } - // check for requested scale - if (data.scale > 24) - { - if (img.height() > data.scale) - { - img = img.scaledToHeight(data.scale); - } - if (img.width() > data.scale) - { - img = img.scaledToWidth(data.scale); - } - } + // check for requested scale + if (data.scale > 24) + { + if (img.height() > data.scale) + { + img = img.scaledToHeight(data.scale); + } + if (img.width() > data.scale) + { + img = img.scaledToWidth(data.scale); + } + } - // check if we need to force a scale - if (img.width() > 2000 || img.height() > 2000) - { - data.scale = 2000; - if (img.height() > data.scale) - { - img = img.scaledToHeight(data.scale); - } - if (img.width() > data.scale) - { - img = img.scaledToWidth(data.scale); - } - } + // check if we need to force a scale + if (img.width() > IMAGE_WIDTH_MAX || img.height() > IMAGE_HEIGHT_MAX) + { + data.scale = IMAGE_SCALE; + if (img.height() > data.scale) + { + img = img.scaledToHeight(data.scale); + } + if (img.width() > data.scale) + { + img = img.scaledToWidth(data.scale); + } + } - data.width = img.width(); - data.height = img.height(); + data.width = img.width(); + data.height = img.height(); - // extract image - img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); - data.data.clear(); - data.data.reserve(img.width() * img.height() * 3); - for (int i = 0; i < img.height(); ++i) - { - const QRgb *scanline = reinterpret_cast(img.scanLine(i)); - for (int j = 0; j < img.width(); ++j) - { - data.data.append((char)qRed(scanline[j])); - data.data.append((char)qGreen(scanline[j])); - data.data.append((char)qBlue(scanline[j])); - } - } - } - else - { - // check consistency of the size of the received data - if (data.data.size() != data.width * data.height * 3) - { - replyMsg = "Size of image data does not match with the width and height"; - return false; - } - } + // extract image + img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); + data.data.clear(); + data.data.reserve(static_cast(img.width() * img.height() * 3)); + for (int i = 0; i < img.height(); ++i) + { + const QRgb *scanline = reinterpret_cast(img.scanLine(i)); + for (int j = 0; j < img.width(); ++j) + { + data.data.append(static_cast(qRed(scanline[j]))); + data.data.append(static_cast(qGreen(scanline[j]))); + data.data.append(static_cast(qBlue(scanline[j]))); + } + } + } + else + { + // check consistency of the size of the received data + if (static_cast(data.data.size()) != data.width * data.height * 3) + { + replyMsg = "Size of image data does not match with the width and height"; + return false; + } + } - // copy image - Image image(data.width, data.height); - memcpy(image.memptr(), data.data.data(), data.data.size()); + // copy image + Image image(data.width, data.height); + memcpy(image.memptr(), data.data.data(), static_cast(data.data.size())); - QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(hyperion::Components, comp), Q_ARG(QString, data.origin), Q_ARG(QString, data.imgName)); - QMetaObject::invokeMethod(_hyperion, "setInputImage", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(Image, image), Q_ARG(int64_t, data.duration)); + QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(hyperion::Components, comp), Q_ARG(QString, data.origin), Q_ARG(QString, data.imgName)); + QMetaObject::invokeMethod(_hyperion, "setInputImage", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(Image, image), Q_ARG(int64_t, data.duration)); - return true; + return true; } -bool API::clearPriority(int priority, QString &replyMsg, hyperion::Components callerComp) +bool API::clearPriority(int priority, QString &replyMsg, hyperion::Components /*callerComp*/) { - if (priority < 0 || (priority > 0 && priority < 254)) - { - QMetaObject::invokeMethod(_hyperion, "clear", Qt::QueuedConnection, Q_ARG(int, priority)); - } - else - { - replyMsg = QString("Priority %1 is not allowed to be cleared").arg(priority); - return false; - } - return true; + if (priority < 0 || (priority > 0 && priority < PriorityMuxer::BG_PRIORITY)) + { + QMetaObject::invokeMethod(_hyperion, "clear", Qt::QueuedConnection, Q_ARG(int, priority)); + } + else + { + replyMsg = QString("Priority %1 is not allowed to be cleared").arg(priority); + return false; + } + return true; } -bool API::setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components callerComp) +bool API::setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components /*callerComp*/) { - Components component = stringToComponent(comp); + Components component = stringToComponent(comp); - if (component != COMP_INVALID) - { - QMetaObject::invokeMethod(_hyperion, "compStateChangeRequest", Qt::QueuedConnection, Q_ARG(hyperion::Components, component), Q_ARG(bool, compState)); - return true; - } - replyMsg = QString("Unknown component name: %1").arg(comp); - return false; + if (component != COMP_INVALID) + { + QMetaObject::invokeMethod(_hyperion, "compStateChangeRequest", Qt::QueuedConnection, Q_ARG(hyperion::Components, component), Q_ARG(bool, compState)); + return true; + } + replyMsg = QString("Unknown component name: %1").arg(comp); + return false; } -void API::setLedMappingType(int type, hyperion::Components callerComp) +void API::setLedMappingType(int type, hyperion::Components /*callerComp*/) { - QMetaObject::invokeMethod(_hyperion, "setLedMappingType", Qt::QueuedConnection, Q_ARG(int, type)); + QMetaObject::invokeMethod(_hyperion, "setLedMappingType", Qt::QueuedConnection, Q_ARG(int, type)); } -void API::setVideoMode(VideoMode mode, hyperion::Components callerComp) +void API::setVideoMode(VideoMode mode, hyperion::Components /*callerComp*/) { - QMetaObject::invokeMethod(_hyperion, "setVideoMode", Qt::QueuedConnection, Q_ARG(VideoMode, mode)); + QMetaObject::invokeMethod(_hyperion, "setVideoMode", Qt::QueuedConnection, Q_ARG(VideoMode, mode)); } #if defined(ENABLE_EFFECTENGINE) -bool 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::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::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)); - } + int isStarted; + if (!dat.args.isEmpty()) + { + QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, isStarted), 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::BlockingQueuedConnection, Q_RETURN_ARG(int, isStarted), Q_ARG(QString, dat.effectName), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.origin)); + } - return res >= 0; + return isStarted >= 0; } #endif -void API::setSourceAutoSelect(bool state, hyperion::Components callerComp) +void API::setSourceAutoSelect(bool state, hyperion::Components /*callerComp*/) { - QMetaObject::invokeMethod(_hyperion, "setSourceAutoSelect", Qt::QueuedConnection, Q_ARG(bool, state)); + QMetaObject::invokeMethod(_hyperion, "setSourceAutoSelect", Qt::QueuedConnection, Q_ARG(bool, state)); } -void API::setVisiblePriority(int priority, hyperion::Components callerComp) +void API::setVisiblePriority(int priority, hyperion::Components /*callerComp*/) { - QMetaObject::invokeMethod(_hyperion, "setVisiblePriority", Qt::QueuedConnection, Q_ARG(int, priority)); + QMetaObject::invokeMethod(_hyperion, "setVisiblePriority", Qt::QueuedConnection, Q_ARG(int, priority)); } void API::registerInput(int priority, hyperion::Components component, const QString &origin, const QString &owner, hyperion::Components callerComp) { - if (_activeRegisters.count(priority)) - _activeRegisters.erase(priority); + if (_activeRegisters.count(priority) != 0) + { + _activeRegisters.erase(priority); + } - _activeRegisters.insert({priority, registerData{component, origin, owner, callerComp}}); + _activeRegisters.insert({priority, registerData{component, origin, owner, callerComp}}); - QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(hyperion::Components, component), Q_ARG(QString, origin), Q_ARG(QString, owner)); + QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(hyperion::Components, component), Q_ARG(QString, origin), Q_ARG(QString, owner)); } void API::unregisterInput(int priority) { - if (_activeRegisters.count(priority)) - _activeRegisters.erase(priority); + if (_activeRegisters.count(priority) != 0) + { + _activeRegisters.erase(priority); + } } bool API::setHyperionInstance(quint8 inst) { - if (_currInstanceIndex == inst) - return true; - bool isRunning; - QMetaObject::invokeMethod(_instanceManager, "IsInstanceRunning", Qt::DirectConnection, Q_RETURN_ARG(bool, isRunning), Q_ARG(quint8, inst)); - if (!isRunning) - return false; + if (_currInstanceIndex == inst) + { + return true; + } - disconnect(_hyperion, 0, this, 0); - QMetaObject::invokeMethod(_instanceManager, "getHyperionInstance", Qt::DirectConnection, Q_RETURN_ARG(Hyperion *, _hyperion), Q_ARG(quint8, inst)); - _currInstanceIndex = inst; - return true; + bool isRunning; + QMetaObject::invokeMethod(_instanceManager, "IsInstanceRunning", Qt::DirectConnection, Q_RETURN_ARG(bool, isRunning), Q_ARG(quint8, inst)); + if (!isRunning) + { + return false; + } + + disconnect(_hyperion, nullptr, this, nullptr); + QMetaObject::invokeMethod(_instanceManager, "getHyperionInstance", Qt::DirectConnection, Q_RETURN_ARG(Hyperion *, _hyperion), Q_ARG(quint8, inst)); + _currInstanceIndex = inst; + return true; } bool API::isHyperionEnabled() { - int res; - QMetaObject::invokeMethod(_hyperion, "isComponentEnabled", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, res), Q_ARG(hyperion::Components, hyperion::COMP_ALL)); - return res > 0; + int isEnabled; + QMetaObject::invokeMethod(_hyperion, "isComponentEnabled", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, isEnabled), Q_ARG(hyperion::Components, hyperion::COMP_ALL)); + return isEnabled > 0; } -QVector API::getAllInstanceData() +QVector API::getAllInstanceData() const { - QVector vec; - QMetaObject::invokeMethod(_instanceManager, "getInstanceData", Qt::DirectConnection, Q_RETURN_ARG(QVector, vec)); - return vec; + QVector vec; + QMetaObject::invokeMethod(_instanceManager, "getInstanceData", Qt::DirectConnection, Q_RETURN_ARG(QVector, vec)); + return vec; } bool API::startInstance(quint8 index, int tan) { - 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); + bool isStarted; + (_instanceManager->thread() != this->thread()) + ? QMetaObject::invokeMethod(_instanceManager, "startInstance", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isStarted), Q_ARG(quint8, index), Q_ARG(bool, false), Q_ARG(QObject*, this), Q_ARG(int, tan)) + : isStarted = _instanceManager->startInstance(index, false, this, tan); - return res; + return isStarted; } void API::stopInstance(quint8 index) { - QMetaObject::invokeMethod(_instanceManager, "stopInstance", Qt::QueuedConnection, Q_ARG(quint8, index)); + QMetaObject::invokeMethod(_instanceManager, "stopInstance", Qt::QueuedConnection, Q_ARG(quint8, index)); } bool API::deleteInstance(quint8 index, QString &replyMsg) { - if (_adminAuthorized) - { - QMetaObject::invokeMethod(_instanceManager, "deleteInstance", Qt::QueuedConnection, Q_ARG(quint8, index)); - return true; - } - replyMsg = NO_AUTH; - return false; + if (_adminAuthorized) + { + QMetaObject::invokeMethod(_instanceManager, "deleteInstance", Qt::QueuedConnection, Q_ARG(quint8, index)); + return true; + } + replyMsg = NO_AUTHORIZATION; + return false; } QString API::createInstance(const QString &name) { - if (_adminAuthorized) - { - bool success; - QMetaObject::invokeMethod(_instanceManager, "createInstance", Qt::DirectConnection, Q_RETURN_ARG(bool, success), Q_ARG(QString, name)); - if (!success) - return QString("Instance name '%1' is already in use").arg(name); - - return ""; - } - return NO_AUTH; + if (_adminAuthorized) + { + bool success; + QMetaObject::invokeMethod(_instanceManager, "createInstance", Qt::DirectConnection, Q_RETURN_ARG(bool, success), Q_ARG(QString, name)); + if (!success) + { + return QString("Instance name '%1' is already in use").arg(name); + } + return ""; + } + return NO_AUTHORIZATION; } QString API::setInstanceName(quint8 index, const QString &name) { - if (_adminAuthorized) - { - QMetaObject::invokeMethod(_instanceManager, "saveName", Qt::QueuedConnection, Q_ARG(quint8, index), Q_ARG(QString, name)); - return ""; - } - return NO_AUTH; + if (_adminAuthorized) + { + QMetaObject::invokeMethod(_instanceManager, "saveName", Qt::QueuedConnection, Q_ARG(quint8, index), Q_ARG(QString, name)); + return ""; + } + return NO_AUTHORIZATION; } #if defined(ENABLE_EFFECTENGINE) QString API::deleteEffect(const QString &name) { - if (_adminAuthorized) - { - QString res; - QMetaObject::invokeMethod(_hyperion, "deleteEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QString, name)); - return res; - } - return NO_AUTH; + if (_adminAuthorized) + { + QString res; + QMetaObject::invokeMethod(_hyperion, "deleteEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QString, name)); + return res; + } + return NO_AUTHORIZATION; } QString API::saveEffect(const QJsonObject &data) { - if (_adminAuthorized) - { - QString res; - QMetaObject::invokeMethod(_hyperion, "saveEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QJsonObject, data)); - return res; - } - return NO_AUTH; + if (_adminAuthorized) + { + QString res; + QMetaObject::invokeMethod(_hyperion, "saveEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QJsonObject, data)); + return res; + } + return NO_AUTHORIZATION; } #endif bool API::saveSettings(const QJsonObject &data) { - bool rc = true; - if (!_adminAuthorized) + bool isSaved {true}; + if (!_adminAuthorized) { - rc = false; + isSaved = false; } else { - QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, rc), Q_ARG(QJsonObject, data), Q_ARG(bool, true)); + QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, isSaved), Q_ARG(QJsonObject, data), Q_ARG(bool, true)); } - return rc; + return isSaved; } bool API::restoreSettings(const QJsonObject &data) { - bool rc = true; + bool isRestored {true}; if (!_adminAuthorized) { - rc = false; + isRestored = false; } else { - QMetaObject::invokeMethod(_hyperion, "restoreSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, rc), Q_ARG(QJsonObject, data), Q_ARG(bool, true)); + QMetaObject::invokeMethod(_hyperion, "restoreSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, isRestored), Q_ARG(QJsonObject, data), Q_ARG(bool, true)); } - return rc; + return isRestored; } bool API::updateHyperionPassword(const QString &password, const QString &newPassword) { - if (!_adminAuthorized) - return false; - bool res; - QMetaObject::invokeMethod(_authManager, "updateUserPassword", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, password), Q_ARG(QString, newPassword)); - return res; + bool isPwUpdated {true}; + if (!_adminAuthorized) + { + isPwUpdated = false; + } + else + { + QMetaObject::invokeMethod(_authManager, "updateUserPassword", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isPwUpdated), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, password), Q_ARG(QString, newPassword)); + } + return isPwUpdated; } QString API::createToken(const QString &comment, AuthManager::AuthDefinition &def) { - if (!_adminAuthorized) - return NO_AUTH; - if (comment.isEmpty()) - return "comment is empty"; - QMetaObject::invokeMethod(_authManager, "createToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AuthManager::AuthDefinition, def), Q_ARG(QString, comment)); - return ""; + if (!_adminAuthorized) + { + return NO_AUTHORIZATION; + } + + if (comment.isEmpty()) + { + return "Missing token comment"; + } + QMetaObject::invokeMethod(_authManager, "createToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AuthManager::AuthDefinition, def), Q_ARG(QString, comment)); + return ""; } -QString API::renameToken(const QString &id, const QString &comment) +QString API::renameToken(const QString &tokenId, const QString &comment) { - if (!_adminAuthorized) - return NO_AUTH; - if (comment.isEmpty() || id.isEmpty()) - return "Empty comment or id"; + if (!_adminAuthorized) + { + return NO_AUTHORIZATION; + } - QMetaObject::invokeMethod(_authManager, "renameToken", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(QString, comment)); - return ""; + if (comment.isEmpty()) + { + return "Missing token comment"; + } + + if (tokenId.isEmpty()) { + return "Missing token id"; + } + + bool isTokenRenamed {false}; + QMetaObject::invokeMethod(_authManager, "renameToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isTokenRenamed), Q_ARG(QString, tokenId), Q_ARG(QString, comment)); + + return (!isTokenRenamed) ? "Token does not exist" : ""; } -QString API::deleteToken(const QString &id) +QString API::deleteToken(const QString &tokenId) { - if (!_adminAuthorized) - return NO_AUTH; - if (id.isEmpty()) - return "Empty id"; + if (!_adminAuthorized) + { + return NO_AUTHORIZATION; + } - QMetaObject::invokeMethod(_authManager, "deleteToken", Qt::QueuedConnection, Q_ARG(QString, id)); - return ""; + if (tokenId.isEmpty()) + { + return "Missing token id"; + } + + bool isTokenDeleted {false}; + QMetaObject::invokeMethod(_authManager, "deleteToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isTokenDeleted), Q_ARG(QString, tokenId)); + + return (!isTokenDeleted) ? "Token does not exist" : ""; } -void API::setNewTokenRequest(const QString &comment, const QString &id, const int &tan) +void API::setNewTokenRequest(const QString &comment, const QString &tokenId, const int &tan) { - QMetaObject::invokeMethod(_authManager, "setNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, id), Q_ARG(int, tan)); + QMetaObject::invokeMethod(_authManager, "setNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, tokenId), Q_ARG(int, tan)); } -void API::cancelNewTokenRequest(const QString &comment, const QString &id) +void API::cancelNewTokenRequest(const QString &comment, const QString &tokenId) { - QMetaObject::invokeMethod(_authManager, "cancelNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, id)); + QMetaObject::invokeMethod(_authManager, "cancelNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, tokenId)); } -bool API::handlePendingTokenRequest(const QString &id, bool accept) +bool API::handlePendingTokenRequest(const QString &tokenId, bool accept) { - if (!_adminAuthorized) - return false; - QMetaObject::invokeMethod(_authManager, "handlePendingTokenRequest", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(bool, accept)); - return true; + if (!_adminAuthorized) + { + return false; + } + QMetaObject::invokeMethod(_authManager, "handlePendingTokenRequest", Qt::QueuedConnection, Q_ARG(QString, tokenId), Q_ARG(bool, accept)); + return true; } bool API::getTokenList(QVector &def) { - if (!_adminAuthorized) - return false; - QMetaObject::invokeMethod(_authManager, "getTokenList", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, def)); - return true; + if (!_adminAuthorized) + { + return false; + } + QMetaObject::invokeMethod(_authManager, "getTokenList", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, def)); + return true; } bool API::getPendingTokenRequests(QVector &map) { - if (!_adminAuthorized) - return false; - QMetaObject::invokeMethod(_authManager, "getPendingRequests", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, map)); - return true; + if (!_adminAuthorized) + { + return false; + } + QMetaObject::invokeMethod(_authManager, "getPendingRequests", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, map)); + return true; } bool API::isUserTokenAuthorized(const QString &userToken) { - bool res; - QMetaObject::invokeMethod(_authManager, "isUserTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, userToken)); - if (res) - { - _authorized = true; - _adminAuthorized = true; - // Listen for ADMIN ACCESS protected signals - connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest, Qt::UniqueConnection); - } - return res; + QMetaObject::invokeMethod(_authManager, "isUserTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _authorized), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, userToken)); + _adminAuthorized = _authorized; + + if (_authorized) + { + // Listen for ADMIN ACCESS protected signals + connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest); + } + else + { + disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest); + } + return _authorized; } bool API::getUserToken(QString &userToken) { - if (!_adminAuthorized) - return false; - QMetaObject::invokeMethod(_authManager, "getUserToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userToken)); - return true; + if (!_adminAuthorized) + { + return false; + } + QMetaObject::invokeMethod(_authManager, "getUserToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userToken)); + return true; } bool API::isTokenAuthorized(const QString &token) { (_authManager->thread() != this->thread()) - ? QMetaObject::invokeMethod(_authManager, "isTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _authorized), Q_ARG(QString, token)) - : _authorized = _authManager->isTokenAuthorized(token); + ? QMetaObject::invokeMethod(_authManager, "isTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _authorized), Q_ARG(QString, token)) + : _authorized = _authManager->isTokenAuthorized(token); + _adminAuthorized = _authorized; - return _authorized; + return _authorized; } bool API::isUserAuthorized(const QString &password) { - bool res; - QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, password)); - if (res) - { - _authorized = true; - _adminAuthorized = true; - // Listen for ADMIN ACCESS protected signals - connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest, Qt::UniqueConnection); - } - return res; + bool isUserAuthorized; + QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isUserAuthorized), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, password)); + if (isUserAuthorized) + { + _authorized = true; + _adminAuthorized = true; + + // Listen for ADMIN ACCESS protected signals + connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest); + } + else + { + disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest); + } + return isUserAuthorized; } bool API::hasHyperionDefaultPw() { - bool res; - QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, DEFAULT_PASSWORD)); - return res; + bool isDefaultPassort; + QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isDefaultPassort), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, DEFAULT_PASSWORD)); + return isDefaultPassort; } void API::logout() { - _authorized = false; - _adminAuthorized = false; - // Stop listenig for ADMIN ACCESS protected signals - disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest); - stopDataConnectionss(); -} - -void API::stopDataConnectionss() -{ + _authorized = false; + _adminAuthorized = false; + // Stop listenig for ADMIN ACCESS protected signals + disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest); + stopDataConnections(); } diff --git a/libsrc/api/CMakeLists.txt b/libsrc/api/CMakeLists.txt index ac5cdcfd..6818e752 100644 --- a/libsrc/api/CMakeLists.txt +++ b/libsrc/api/CMakeLists.txt @@ -2,10 +2,14 @@ add_library(hyperion-api ${CMAKE_SOURCE_DIR}/include/api/apiStructs.h ${CMAKE_SOURCE_DIR}/include/api/API.h ${CMAKE_SOURCE_DIR}/include/api/JsonAPI.h - ${CMAKE_SOURCE_DIR}/include/api/JsonCB.h + ${CMAKE_SOURCE_DIR}/include/api/JsonCallbacks.h + ${CMAKE_SOURCE_DIR}/include/api/JsonApiCommand.h + ${CMAKE_SOURCE_DIR}/include/api/JsonApiSubscription.h + ${CMAKE_SOURCE_DIR}/include/api/JsonInfo.h ${CMAKE_SOURCE_DIR}/libsrc/api/JsonAPI.cpp ${CMAKE_SOURCE_DIR}/libsrc/api/API.cpp - ${CMAKE_SOURCE_DIR}/libsrc/api/JsonCB.cpp + ${CMAKE_SOURCE_DIR}/libsrc/api/JsonCallbacks.cpp + ${CMAKE_SOURCE_DIR}/libsrc/api/JsonInfo.cpp ${CMAKE_SOURCE_DIR}/libsrc/api/JSONRPC_schemas.qrc ) diff --git a/libsrc/api/JSONRPC_schema/schema-ledcolors.json b/libsrc/api/JSONRPC_schema/schema-ledcolors.json index b1bda0a3..e0f3d905 100644 --- a/libsrc/api/JSONRPC_schema/schema-ledcolors.json +++ b/libsrc/api/JSONRPC_schema/schema-ledcolors.json @@ -13,15 +13,7 @@ "subcommand": { "type" : "string", "required" : true, - "enum" : ["ledstream-stop","ledstream-start","testled","imagestream-start","imagestream-stop"] - }, - "oneshot": { - "type" : "bool" - }, - "interval": { - "type" : "integer", - "required" : false, - "minimum": 50 + "enum" : ["ledstream-stop","ledstream-start","imagestream-start","imagestream-stop"] } }, diff --git a/libsrc/api/JSONRPC_schema/schema-serverinfo.json b/libsrc/api/JSONRPC_schema/schema-serverinfo.json index 990eec04..4ead4617 100644 --- a/libsrc/api/JSONRPC_schema/schema-serverinfo.json +++ b/libsrc/api/JSONRPC_schema/schema-serverinfo.json @@ -7,6 +7,20 @@ "required" : true, "enum" : ["serverinfo"] }, + "subcommand": { + "type": "string", + "enum": ["getInfo", "subscribe", "unsubscribe", "getSubscriptions", "getSubscriptionCommands"] + }, + "data": { + "type": ["null", "array"], + "properties": { + "subscriptions": { + "type": "array", + "items": {} + } + }, + "additionalProperties": false + }, "subscribe" : { "type" : "array" }, diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index 45b9594a..19b4aef9 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -1,5 +1,6 @@ // project includes #include +#include // Qt includes #include @@ -10,8 +11,8 @@ #include #include #include -#include -#include +#include +#include // hyperion includes #include @@ -20,62 +21,11 @@ #include // Required to determine the cmake options -#include -#include - #include #include -#if defined(ENABLE_MF) - #include -#elif defined(ENABLE_V4L2) - #include -#endif - -#if defined(ENABLE_AUDIO) - #include - - #ifdef WIN32 - #include - #endif - - #ifdef __linux__ - #include - #endif -#endif - -#if defined(ENABLE_X11) - #include -#endif - -#if defined(ENABLE_XCB) - #include -#endif - -#if defined(ENABLE_DX) - #include -#endif - -#if defined(ENABLE_FB) - #include -#endif - -#if defined(ENABLE_DISPMANX) - #include -#endif - -#if defined(ENABLE_AMLOGIC) - #include -#endif - -#if defined(ENABLE_OSX) - #include -#endif - #include #include -#include -#include #include #include #include @@ -84,7 +34,7 @@ #include // api includes -#include +#include #include // auth manager @@ -99,42 +49,52 @@ #include #endif +#include +#include + using namespace hyperion; // Constants -namespace { const bool verbose = false; } +namespace { + +constexpr std::chrono::milliseconds NEW_TOKEN_REQUEST_TIMEOUT{ 180000 }; + +const char TOKEN_TAG[] = "token"; +constexpr int TOKEN_TAG_LENGTH = sizeof(TOKEN_TAG) - 1; +const char BEARER_TOKEN_TAG[] = "Bearer"; +constexpr int BEARER_TOKEN_TAG_LENGTH = sizeof(BEARER_TOKEN_TAG) - 1; + +const int MIN_PASSWORD_LENGTH = 8; +const int APP_TOKEN_LENGTH = 36; + +const bool verbose = false; +} JsonAPI::JsonAPI(QString peerAddress, Logger *log, bool localConnection, QObject *parent, bool noListener) : API(log, localConnection, parent) + ,_noListener(noListener) + ,_peerAddress (std::move(peerAddress)) + ,_jsonCB (nullptr) { - _noListener = noListener; - _peerAddress = peerAddress; - _jsonCB = new JsonCB(this); - _streaming_logging_activated = false; - _ledStreamTimer = new QTimer(this); - Q_INIT_RESOURCE(JSONRPC_schemas); qRegisterMetaType("Event"); + _jsonCB = QSharedPointer(new JsonCallbacks( _log, _peerAddress, parent)); } void JsonAPI::initialize() { - Debug(_log,""); // init API, REQUIRED! API::init(); - // Initialise jsonCB with current instance - _jsonCB->setSubscriptionsTo(_hyperion); // setup auth interface - connect(this, &API::onPendingTokenRequest, this, &JsonAPI::newPendingTokenRequest); - connect(this, &API::onTokenResponse, this, &JsonAPI::handleTokenResponse); + connect(this, &API::onPendingTokenRequest, this, &JsonAPI::issueNewPendingTokenRequest); // listen for killed instances connect(_instanceManager, &HyperionIManager::instanceStateChanged, this, &JsonAPI::handleInstanceStateChange); // pipe callbacks from subscriptions to parent - connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage); + connect(_jsonCB.data(), &JsonCallbacks::newCallback, this, &JsonAPI::callbackMessage); // notify hyperion about a jsonMessageForward if (_hyperion != nullptr) @@ -146,8 +106,6 @@ void JsonAPI::initialize() //notify eventhadler on suspend/resume/idle requests connect(this, &JsonAPI::signalEvent, EventHandler::getInstance().data(), &EventHandler::handleEvent); - - connect(_ledStreamTimer, &QTimer::timeout, this, &JsonAPI::streamLedColorsUpdate, Qt::UniqueConnection); } bool JsonAPI::handleInstanceSwitch(quint8 inst, bool /*forced*/) @@ -164,129 +122,202 @@ bool JsonAPI::handleInstanceSwitch(quint8 inst, bool /*forced*/) void JsonAPI::handleMessage(const QString &messageString, const QString &httpAuthHeader) { - Debug(_log,""); const QString ident = "JsonRpc@" + _peerAddress; QJsonObject message; - //std::cout << "JsonAPI::handleMessage | [" << static_cast(_hyperion->getInstanceIndex()) << "] Received: ["<< messageString.toStdString() << "]" << std::endl; - //std::cout << "JsonAPI::handleMessage - _noListener [" << _noListener << "]" << std::endl; - //std::cout << "JsonAPI::handleMessage - _authorized [" << _authorized << "] _adminAuthorized [" << _adminAuthorized << "]" << std::endl; - // parse the message - if (!JsonUtils::parse(ident, messageString, message, _log)) + //parse the message + QPair parsingResult = JsonUtils::parse(ident, messageString, message, _log); + if (!parsingResult.first) { - sendErrorReply("Errors during message parsing, please consult the Hyperion Log."); + //Try to find command and tan, even parsing failed + QString command = findCommand(messageString); + int tan = findTan(messageString); + + sendErrorReply("Parse error", parsingResult.second, command, tan); return; } - int tan = 0; - if (message.value("tan") != QJsonValue::Undefined) - tan = message["tan"].toInt(); - - // check basic message - if (!JsonUtils::validate(ident, message, ":schema", _log)) - { - sendErrorReply("Errors during message validation, please consult the Hyperion Log.", "" /*command*/, tan); - return; - } + DebugIf(verbose, _log, "message: [%s]", QJsonDocument(message).toJson(QJsonDocument::Compact).constData() ); // check specific message - const QString command = message["command"].toString(); - if (!JsonUtils::validate(ident, message, QString(":schema-%1").arg(command), _log)) + const QString command = message.value("command").toString(); + const QString subCommand = message.value("subcommand").toString(); + + int tan {0}; + if (message.value("tan") != QJsonValue::Undefined) { - sendErrorReply("Errors during specific message validation, please consult the Hyperion Log", command, tan); + tan = message["tan"].toInt(); + } + + // check basic message + QJsonObject schemaJson = QJsonFactory::readSchema(":schema"); + QPair validationResult = JsonUtils::validate(ident, message, schemaJson, _log); + if (!validationResult.first) + { + sendErrorReply("Invalid command", validationResult.second, command, tan); return; } - // check auth state - if (!API::isAuthorized()) + JsonApiCommand cmd = ApiCommandRegister::getCommandInfo(command, subCommand); + cmd.tan = tan; + + if (cmd.command == Command::Unknown) { - Debug(_log,"!API::isAuthorized(), _noListener [%d]", _noListener); - // on the fly auth available for http from http Auth header - if (_noListener) + const QStringList errorDetails (subCommand.isEmpty() ? "subcommand is missing" : QString("Invalid subcommand: %1").arg(subCommand)); + sendErrorReply("Invalid command", errorDetails, command, tan); + return; + } + + if (_noListener) + { + if(cmd.isNolistenerCmd == NoListenerCmd::No) { - QString cToken = httpAuthHeader.mid(5).trimmed(); - if (API::isTokenAuthorized(cToken)) - { - _authorized = true; - if (!_authManager->isLocalAdminAuthRequired()) - { - _adminAuthorized = true; - } - goto proceed; + sendErrorReply("Command not supported via single API calls using HTTP/S", cmd); + return; + } + + // Check authorization for HTTP requests + if (!httpAuthHeader.isEmpty()) + { + int bearTokenLenght {0}; + if (httpAuthHeader.startsWith(BEARER_TOKEN_TAG, Qt::CaseInsensitive)) { + bearTokenLenght = BEARER_TOKEN_TAG_LENGTH; } - sendErrorReply("No Authorization", command, tan); + else if (httpAuthHeader.startsWith(TOKEN_TAG, Qt::CaseInsensitive)) { + bearTokenLenght = TOKEN_TAG_LENGTH; + } + + if (bearTokenLenght == 0) + { + sendErrorReply("No bearer token found in Authorization header", cmd); + return; + } + + QString cToken =httpAuthHeader.mid(bearTokenLenght).trimmed(); + API::isTokenAuthorized(cToken); // _authorized && _adminAuthorized are set + } + + if (islocalConnection() && !_authManager->isLocalAuthRequired()) + { + // if the request comes via a local network connection, plus authorization is disabled for local request, + // no token authorization is required for non-admin requests + setAuthorization(true); + } + } + + if (cmd.authorization != Authorization::No ) + { + if (!isAuthorized() || (cmd.authorization == Authorization::Admin && !isAdminAuthorized())) + { + sendNoAuthorization(cmd); return; } } -proceed: - if (_hyperion == nullptr) + + schemaJson = QJsonFactory::readSchema(QString(":schema-%1").arg(command)); + validationResult = JsonUtils::validate(ident, message, schemaJson, _log); + if (!validationResult.first) { - sendErrorReply("Service Unavailable", command, tan); + sendErrorReply("Invalid params", validationResult.second, cmd); return; } - //Debug(_log,"proceed - cmd: [%s], _authorized[%d], _adminAuthorized[%d]", QSTRING_CSTR(command), _authorized, _adminAuthorized); - // switch over all possible commands and handle them - if (command == "authorize") - handleAuthorizeCommand(message, command, tan); - else if (command == "color") - handleColorCommand(message, command, tan); - else if (command == "image") - handleImageCommand(message, command, tan); -#if defined(ENABLE_EFFECTENGINE) - else if (command == "effect") - handleEffectCommand(message, command, tan); - else if (command == "create-effect") - handleCreateEffectCommand(message, command, tan); - else if (command == "delete-effect") - handleDeleteEffectCommand(message, command, tan); -#endif - else if (command == "sysinfo") - handleSysInfoCommand(message, command, tan); - else if (command == "serverinfo") - handleServerInfoCommand(message, command, tan); - else if (command == "clear") - handleClearCommand(message, command, tan); - else if (command == "adjustment") - handleAdjustmentCommand(message, command, tan); - else if (command == "sourceselect") - handleSourceSelectCommand(message, command, tan); - else if (command == "config") - handleConfigCommand(message, command, tan); - else if (command == "componentstate") - handleComponentStateCommand(message, command, tan); - else if (command == "ledcolors") - handleLedColorsCommand(message, command, tan); - 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); - else if (command == "leddevice") - handleLedDeviceCommand(message, command, tan); - else if (command == "inputsource") - handleInputSourceCommand(message, command, tan); - else if (command == "service") - handleServiceCommand(message, command, tan); - else if (command == "system") - handleSystemCommand(message, command, tan); + if (_hyperion == nullptr) + { + sendErrorReply("Service Unavailable", cmd); + return; + } - // BEGIN | The following commands are deprecated but used to ensure backward compatibility with hyperion Classic remote control - 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", command, tan); - // END - - // handle not implemented commands - else - handleNotImplemented(command, tan); + handleCommand(cmd, message); } -void JsonAPI::handleColorCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleCommand(const JsonApiCommand& cmd, const QJsonObject &message) +{ + switch (cmd.command) { + case Command::Authorize: + handleAuthorizeCommand(message, cmd); + break; + case Command::Color: + handleColorCommand(message, cmd); + break; + case Command::Image: + handleImageCommand(message, cmd); + break; +#if defined(ENABLE_EFFECTENGINE) + case Command::Effect: + handleEffectCommand(message, cmd); + break; + case Command::CreateEffect: + handleCreateEffectCommand(message, cmd); + break; + case Command::DeleteEffect: + handleDeleteEffectCommand(message, cmd); + break; +#endif + case Command::SysInfo: + handleSysInfoCommand(message, cmd); + break; + case Command::ServerInfo: + handleServerInfoCommand(message, cmd); + break; + case Command::Clear: + handleClearCommand(message, cmd); + break; + case Command::Adjustment: + handleAdjustmentCommand(message, cmd); + break; + case Command::SourceSelect: + handleSourceSelectCommand(message, cmd); + break; + case Command::Config: + handleConfigCommand(message, cmd); + break; + case Command::ComponentState: + handleComponentStateCommand(message, cmd); + break; + case Command::LedColors: + handleLedColorsCommand(message, cmd); + break; + case Command::Logging: + handleLoggingCommand(message, cmd); + break; + case Command::Processing: + handleProcessingCommand(message, cmd); + break; + case Command::VideoMode: + handleVideoModeCommand(message, cmd); + break; + case Command::Instance: + handleInstanceCommand(message, cmd); + break; + case Command::LedDevice: + handleLedDeviceCommand(message, cmd); + break; + case Command::InputSource: + handleInputSourceCommand(message, cmd); + break; + case Command::Service: + handleServiceCommand(message, cmd); + break; + case Command::System: + handleSystemCommand(message, cmd); + break; + case Command::ClearAll: + handleClearallCommand(message, cmd); + break; + // BEGIN | The following commands are deprecated but used to ensure backward compatibility with Hyperion Classic remote control + case Command::Transform: + case Command::Correction: + case Command::Temperature: + sendErrorReply("The command is deprecated, please use the Hyperion Web Interface to configure", cmd); + break; + // END + default: + break; + } +} + +void JsonAPI::handleColorCommand(const QJsonObject &message, const JsonApiCommand& cmd) { emit forwardJsonMessage(message); int priority = message["priority"].toInt(); @@ -295,17 +326,16 @@ void JsonAPI::handleColorCommand(const QJsonObject &message, const QString &comm const QJsonArray &jsonColor = message["color"].toArray(); std::vector colors; - // TODO faster copy - for (const auto &entry : jsonColor) - { - colors.emplace_back(uint8_t(entry.toInt())); - } + colors.reserve(static_cast::size_type>(jsonColor.size())); + // Transform each entry in jsonColor to uint8_t and append to colors + std::transform(jsonColor.begin(), jsonColor.end(), std::back_inserter(colors), + [](const QJsonValue &value) { return static_cast(value.toInt()); }); API::setColor(priority, colors, duration, origin); - sendSuccessReply(command, tan); + sendSuccessReply(cmd); } -void JsonAPI::handleImageCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleImageCommand(const QJsonObject &message, const JsonApiCommand& cmd) { emit forwardJsonMessage(message); @@ -321,16 +351,15 @@ void JsonAPI::handleImageCommand(const QJsonObject &message, const QString &comm idata.data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8())); QString replyMsg; - if (!API::setImage(idata, COMP_IMAGE, replyMsg)) - { - sendErrorReply(replyMsg, command, tan); - return; + if (API::setImage(idata, COMP_IMAGE, replyMsg)) { + sendSuccessReply(cmd); + } else { + sendErrorReply(replyMsg, cmd); } - sendSuccessReply(command, tan); } #if defined(ENABLE_EFFECTENGINE) -void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd) { emit forwardJsonMessage(message); @@ -343,524 +372,118 @@ void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &com dat.data = message["imageData"].toString("").toUtf8(); dat.args = message["effect"].toObject()["args"].toObject(); - if (API::setEffect(dat)) - sendSuccessReply(command, tan); - else - sendErrorReply("Effect '" + dat.effectName + "' not found", command, tan); + if (API::setEffect(dat)) { + sendSuccessReply(cmd); + } else { + sendErrorReply("Effect '" + dat.effectName + "' not found", cmd); + } } -void JsonAPI::handleCreateEffectCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleCreateEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd) { const QString resultMsg = API::saveEffect(message); - resultMsg.isEmpty() ? sendSuccessReply(command, tan) : sendErrorReply(resultMsg, command, tan); + resultMsg.isEmpty() ? sendSuccessReply(cmd) : sendErrorReply(resultMsg, cmd); } -void JsonAPI::handleDeleteEffectCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleDeleteEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd) { const QString res = API::deleteEffect(message["name"].toString()); - res.isEmpty() ? sendSuccessReply(command, tan) : sendErrorReply(res, command, tan); + res.isEmpty() ? sendSuccessReply(cmd) : sendErrorReply(res, cmd); } #endif -void JsonAPI::handleSysInfoCommand(const QJsonObject &, const QString &command, int tan) +void JsonAPI::handleSysInfoCommand(const QJsonObject & /*unused*/, const JsonApiCommand& cmd) { - // create result - QJsonObject result; - QJsonObject info; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - SysInfo::HyperionSysInfo data = SysInfo::get(); - QJsonObject system; - system["kernelType"] = data.kernelType; - system["kernelVersion"] = data.kernelVersion; - system["architecture"] = data.architecture; - system["cpuModelName"] = data.cpuModelName; - system["cpuModelType"] = data.cpuModelType; - system["cpuHardware"] = data.cpuHardware; - system["cpuRevision"] = data.cpuRevision; - system["wordSize"] = data.wordSize; - system["productType"] = data.productType; - system["productVersion"] = data.productVersion; - system["prettyName"] = data.prettyName; - system["hostName"] = data.hostName; - system["domainName"] = data.domainName; - system["isUserAdmin"] = data.isUserAdmin; - system["qtVersion"] = data.qtVersion; -#if defined(ENABLE_EFFECTENGINE) - system["pyVersion"] = data.pyVersion; -#endif - info["system"] = system; - - QJsonObject hyperion; - hyperion["version"] = QString(HYPERION_VERSION); - hyperion["build"] = QString(HYPERION_BUILD_ID); - hyperion["gitremote"] = QString(HYPERION_GIT_REMOTE); - hyperion["time"] = QString(__DATE__ " " __TIME__); - hyperion["id"] = _authManager->getID(); - hyperion["rootPath"] = _instanceManager->getRootPath(); - hyperion["readOnlyMode"] = _hyperion->getReadOnlyMode(); - - QCoreApplication* app = QCoreApplication::instance(); - hyperion["isGuiMode"] = qobject_cast(app) ? true : false; - - info["hyperion"] = hyperion; - - // send the result - result["info"] = info; - emit callbackMessage(result); + sendSuccessDataReply(JsonInfo::getSystemInfo(_hyperion), cmd); } -void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const JsonApiCommand& cmd) { - QJsonObject info; + QJsonObject info {}; + QStringList errorDetails; - // collect priority information - QJsonArray priorities; - uint64_t now = QDateTime::currentMSecsSinceEpoch(); - QList activePriorities = _hyperion->getActivePriorities(); - activePriorities.removeAll(PriorityMuxer::LOWEST_PRIORITY); - int currentPriority = _hyperion->getCurrentPriority(); - - for(int priority : std::as_const(activePriorities)) - { - const Hyperion::InputInfo &priorityInfo = _hyperion->getPriorityInfo(priority); - - QJsonObject item; - item["priority"] = priority; - - if (priorityInfo.timeoutTime_ms > 0 ) - { - item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now); - } - - // owner has optional informations to the component - if (!priorityInfo.owner.isEmpty()) - { - item["owner"] = priorityInfo.owner; - } - - item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); - item["origin"] = priorityInfo.origin; - item["active"] = (priorityInfo.timeoutTime_ms >= -1); - item["visible"] = (priority == currentPriority); - - if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) - { - QJsonObject LEDcolor; - - // add RGB Value to Array - QJsonArray RGBValue; - RGBValue.append(priorityInfo.ledColors.begin()->red); - RGBValue.append(priorityInfo.ledColors.begin()->green); - RGBValue.append(priorityInfo.ledColors.begin()->blue); - LEDcolor.insert("RGB", RGBValue); - - uint16_t Hue; - float Saturation; - float Luminace; - - // add HSL Value to Array - QJsonArray HSLValue; - ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, - priorityInfo.ledColors.begin()->green, - priorityInfo.ledColors.begin()->blue, - Hue, Saturation, Luminace); - - HSLValue.append(Hue); - HSLValue.append(Saturation); - HSLValue.append(Luminace); - LEDcolor.insert("HSL", HSLValue); - - item["value"] = LEDcolor; - } - - (priority == currentPriority) - ? priorities.prepend(item) - : priorities.append(item); - } - - info["priorities"] = priorities; - info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled(); - - // collect adjustment information - QJsonArray adjustmentArray; - for (const QString &adjustmentId : _hyperion->getAdjustmentIds()) - { - const ColorAdjustment *colorAdjustment = _hyperion->getAdjustment(adjustmentId); - if (colorAdjustment == nullptr) - { - Error(_log, "Incorrect color adjustment id: %s", QSTRING_CSTR(adjustmentId)); - continue; - } - - QJsonObject adjustment; - adjustment["id"] = adjustmentId; - - QJsonArray whiteAdjust; - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR()); - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG()); - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB()); - adjustment.insert("white", whiteAdjust); - - QJsonArray redAdjust; - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR()); - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG()); - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB()); - adjustment.insert("red", redAdjust); - - QJsonArray greenAdjust; - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR()); - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG()); - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB()); - adjustment.insert("green", greenAdjust); - - QJsonArray blueAdjust; - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR()); - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG()); - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB()); - adjustment.insert("blue", blueAdjust); - - QJsonArray cyanAdjust; - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR()); - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG()); - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB()); - adjustment.insert("cyan", cyanAdjust); - - QJsonArray magentaAdjust; - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR()); - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG()); - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB()); - adjustment.insert("magenta", magentaAdjust); - - QJsonArray yellowAdjust; - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR()); - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG()); - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB()); - adjustment.insert("yellow", yellowAdjust); - - adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold(); - adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored(); - adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness(); - adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation(); - adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR(); - adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG(); - adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB(); - - adjustment["saturationGain"] = colorAdjustment->_okhsvTransform.getSaturationGain(); - adjustment["brightnessGain"] = colorAdjustment->_okhsvTransform.getBrightnessGain(); - - adjustmentArray.append(adjustment); - } - - info["adjustment"] = adjustmentArray; + switch (cmd.getSubCommand()) { + case SubCommand::Empty: + case SubCommand::GetInfo: + info["priorities"] = JsonInfo::getPrioritiestInfo(_hyperion); + info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled(); + info["adjustment"] = JsonInfo::getAdjustmentInfo(_hyperion, _log); + info["ledDevices"] = JsonInfo::getAvailableLedDevices(); + info["grabbers"] = JsonInfo::getGrabbers(_hyperion); + info["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode())); + info["cec"] = JsonInfo::getCecInfo(); + info["services"] = JsonInfo::getServices(); + info["components"] = JsonInfo::getComponents(_hyperion); + info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType()); + info["instance"] = JsonInfo::getInstanceInfo(); + info["leds"] = _hyperion->getSetting(settings::LEDS).array(); + info["activeLedColor"] = JsonInfo::getActiveColors(_hyperion); #if defined(ENABLE_EFFECTENGINE) - // collect effect info - QJsonArray effects; - const std::list &effectsDefinitions = _hyperion->getEffects(); - for (const EffectDefinition &effectDefinition : effectsDefinitions) - { - QJsonObject effect; - effect["name"] = effectDefinition.name; - effect["file"] = effectDefinition.file; - effect["script"] = effectDefinition.script; - effect["args"] = effectDefinition.args; - effects.append(effect); - } - - info["effects"] = effects; + info["effects"] = JsonInfo::getEffects(_hyperion); + info["activeEffects"] = JsonInfo::getActiveEffects(_hyperion); #endif - // get available led devices - QJsonObject ledDevices; - QJsonArray availableLedDevices; - for (auto dev : LedDeviceWrapper::getDeviceMap()) - { - availableLedDevices.append(dev.first); - } + // BEGIN | The following entries are deprecated but used to ensure backward compatibility with hyperion Classic or up to Hyperion 2.0.16 + info["hostname"] = QHostInfo::localHostName(); + info["transform"] = JsonInfo::getTransformationInfo(_hyperion); - ledDevices["available"] = availableLedDevices; - info["ledDevices"] = ledDevices; - - QJsonObject grabbers; - // SCREEN - QJsonObject screenGrabbers; - if (GrabberWrapper::getInstance() != nullptr) - { - QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex(), GrabberTypeFilter::SCREEN); - QJsonArray activeGrabberNames; - for (auto grabberName : activeGrabbers) + if (!_noListener && message.contains("subscribe")) { - activeGrabberNames.append(grabberName); - } - - screenGrabbers["active"] = activeGrabberNames; - } - QJsonArray availableScreenGrabbers; - for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::SCREEN)) - { - availableScreenGrabbers.append(grabber); - } - screenGrabbers["available"] = availableScreenGrabbers; - - // VIDEO - QJsonObject videoGrabbers; - if (GrabberWrapper::getInstance() != nullptr) - { - QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex(), GrabberTypeFilter::VIDEO); - QJsonArray activeGrabberNames; - for (auto grabberName : activeGrabbers) - { - activeGrabberNames.append(grabberName); - } - - videoGrabbers["active"] = activeGrabberNames; - } - QJsonArray availableVideoGrabbers; - for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::VIDEO)) - { - availableVideoGrabbers.append(grabber); - } - videoGrabbers["available"] = availableVideoGrabbers; - - // AUDIO - QJsonObject audioGrabbers; - if (GrabberWrapper::getInstance() != nullptr) - { - QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex(), GrabberTypeFilter::AUDIO); - - QJsonArray activeGrabberNames; - for (auto grabberName : activeGrabbers) - { - activeGrabberNames.append(grabberName); - } - - audioGrabbers["active"] = activeGrabberNames; - } - QJsonArray availableAudioGrabbers; - for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::AUDIO)) - { - availableAudioGrabbers.append(grabber); - } - audioGrabbers["available"] = availableAudioGrabbers; - - grabbers.insert("screen", screenGrabbers); - grabbers.insert("video", videoGrabbers); - grabbers.insert("audio", audioGrabbers); - - info["grabbers"] = grabbers; - - info["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode())); - - QJsonObject cecInfo; -#if defined(ENABLE_CEC) - cecInfo["enabled"] = true; -#else - cecInfo["enabled"] = false; -#endif - info["cec"] = cecInfo; - - // get available services - QJsonArray services; - -#if defined(ENABLE_BOBLIGHT_SERVER) - services.append("boblight"); -#endif - -#if defined(ENABLE_CEC) - services.append("cec"); -#endif - -#if defined(ENABLE_EFFECTENGINE) - services.append("effectengine"); -#endif - -#if defined(ENABLE_FORWARDER) - services.append("forwarder"); -#endif - -#if defined(ENABLE_FLATBUF_SERVER) - services.append("flatbuffer"); -#endif - -#if defined(ENABLE_PROTOBUF_SERVER) - services.append("protobuffer"); -#endif - -#if defined(ENABLE_MDNS) - services.append("mDNS"); -#endif - services.append("SSDP"); - - if (!availableScreenGrabbers.isEmpty() || !availableVideoGrabbers.isEmpty() || services.contains("flatbuffer") || services.contains("protobuffer")) - { - services.append("borderdetection"); - } - - info["services"] = services; - - // get available components - QJsonArray component; - std::map components = _hyperion->getComponentRegister()->getRegister(); - for (auto comp : components) - { - QJsonObject item; - item["name"] = QString::fromStdString(hyperion::componentToIdString(comp.first)); - item["enabled"] = comp.second; - - component.append(item); - } - - info["components"] = component; - info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType()); - - // add instance info - QJsonArray instanceInfo; - for (const auto &entry : API::getAllInstanceData()) - { - QJsonObject obj; - obj.insert("friendly_name", entry["friendly_name"].toString()); - obj.insert("instance", entry["instance"].toInt()); - 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 deprecated but used to ensure backward compatibility with hyperion Classic remote control - // TODO Output the real transformation information instead of default - - // HOST NAME - info["hostname"] = QHostInfo::localHostName(); - - // TRANSFORM INFORMATION (DEFAULT VALUES) - QJsonArray transformArray; - for (const QString &transformId : _hyperion->getAdjustmentIds()) - { - QJsonObject transform; - QJsonArray blacklevel, whitelevel, gamma, threshold; - - transform["id"] = transformId; - transform["saturationGain"] = 1.0; - transform["brightnessGain"] = 1.0; - transform["saturationLGain"] = 1.0; - transform["luminanceGain"] = 1.0; - transform["luminanceMinimum"] = 0.0; - - for (int i = 0; i < 3; i++) - { - blacklevel.append(0.0); - whitelevel.append(1.0); - gamma.append(2.50); - threshold.append(0.0); - } - - transform.insert("blacklevel", blacklevel); - transform.insert("whitelevel", whitelevel); - transform.insert("gamma", gamma); - transform.insert("threshold", threshold); - - transformArray.append(transform); - } - info["transform"] = transformArray; - -#if defined(ENABLE_EFFECTENGINE) - // ACTIVE EFFECT INFO - QJsonArray activeEffects; - for (const ActiveEffectDefinition &activeEffectDefinition : _hyperion->getActiveEffects()) - { - if (activeEffectDefinition.priority != PriorityMuxer::LOWEST_PRIORITY - 1) - { - QJsonObject activeEffect; - activeEffect["script"] = activeEffectDefinition.script; - activeEffect["name"] = activeEffectDefinition.name; - activeEffect["priority"] = activeEffectDefinition.priority; - activeEffect["timeout"] = activeEffectDefinition.timeout; - activeEffect["args"] = activeEffectDefinition.args; - activeEffects.append(activeEffect); - } - } - info["activeEffects"] = activeEffects; -#endif - - // ACTIVE STATIC LED COLOR - QJsonArray activeLedColors; - const Hyperion::InputInfo &priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority()); - if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) - { - // check if LED Color not Black (0,0,0) - if ((priorityInfo.ledColors.begin()->red + - priorityInfo.ledColors.begin()->green + - priorityInfo.ledColors.begin()->blue != - 0)) - { - QJsonObject LEDcolor; - - // add RGB Value to Array - QJsonArray RGBValue; - RGBValue.append(priorityInfo.ledColors.begin()->red); - RGBValue.append(priorityInfo.ledColors.begin()->green); - RGBValue.append(priorityInfo.ledColors.begin()->blue); - LEDcolor.insert("RGB Value", RGBValue); - - uint16_t Hue; - float Saturation, Luminace; - - // add HSL Value to Array - QJsonArray HSLValue; - ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, - priorityInfo.ledColors.begin()->green, - priorityInfo.ledColors.begin()->blue, - Hue, Saturation, Luminace); - - HSLValue.append(Hue); - HSLValue.append(Saturation); - HSLValue.append(Luminace); - LEDcolor.insert("HSL Value", HSLValue); - - activeLedColors.append(LEDcolor); - } - } - info["activeLedColor"] = activeLedColors; - - // END - - sendSuccessDataReply(QJsonDocument(info), command, tan); - - // AFTER we send the info, the client might want to subscribe to future updates - if (message.contains("subscribe")) - { - // check if listeners are allowed - if (_noListener) - return; - - QJsonArray subsArr = message["subscribe"].toArray(); - // catch the all keyword and build a list of all cmds - if (subsArr.contains("all")) - { - subsArr = QJsonArray(); - for (const auto& entry : _jsonCB->getCommands()) + const QJsonArray &subscriptions = message["subscribe"].toArray(); + QStringList invaliCommands = _jsonCB->subscribe(subscriptions); + if (!invaliCommands.isEmpty()) { - subsArr.append(entry); + errorDetails.append("subscribe - Invalid commands provided: " + invaliCommands.join(',')); } } + // END - for (const QJsonValueRef entry : subsArr) + break; + + case SubCommand::Subscribe: + case SubCommand::Unsubscribe: + { + const QJsonObject ¶ms = message["data"].toObject(); + const QJsonArray &subscriptions = params["subscriptions"].toArray(); + if (subscriptions.isEmpty()) { + sendErrorReply("Invalid params", {"No subscriptions provided"}, cmd); + return; + } + + QStringList invaliCommands; + if (cmd.subCommand == SubCommand::Subscribe) { - // config callbacks just if auth is set - if ((entry == "settings-update" || entry == "token-update") && !API::isAdminAuthorized()) - continue; - // silent failure if a subscribe type is not found - _jsonCB->subscribeFor(entry.toString()); + invaliCommands = _jsonCB->subscribe(subscriptions); + } + else + { + invaliCommands = _jsonCB->unsubscribe(subscriptions); + } + + if (!invaliCommands.isEmpty()) + { + errorDetails.append("subscriptions - Invalid commands provided: " + invaliCommands.join(',')); } } + break; + + case SubCommand::GetSubscriptions: + info["subscriptions"] = QJsonArray::fromStringList(_jsonCB->getSubscribedCommands()); + break; + + case SubCommand::GetSubscriptionCommands: + info["commands"] = QJsonArray::fromStringList(_jsonCB->getCommands()); + break; + + default: + break; + } + + sendSuccessDataReplyWithError(info, cmd, errorDetails); } -void JsonAPI::handleClearCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleClearCommand(const QJsonObject &message, const JsonApiCommand& cmd) { emit forwardJsonMessage(message); int priority = message["priority"].toInt(); @@ -868,117 +491,113 @@ void JsonAPI::handleClearCommand(const QJsonObject &message, const QString &comm if (!API::clearPriority(priority, replyMsg)) { - sendErrorReply(replyMsg, command, tan); + sendErrorReply(replyMsg, cmd); return; } - sendSuccessReply(command, tan); + sendSuccessReply(cmd); } -void JsonAPI::handleClearallCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleClearallCommand(const QJsonObject &message, const JsonApiCommand& cmd) { emit forwardJsonMessage(message); QString replyMsg; API::clearPriority(-1, replyMsg); - sendSuccessReply(command, tan); + sendSuccessReply(cmd); } -void JsonAPI::handleAdjustmentCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleAdjustmentCommand(const QJsonObject &message, const JsonApiCommand& cmd) { const QJsonObject &adjustment = message["adjustment"].toObject(); - const QString adjustmentId = adjustment["id"].toString(_hyperion->getAdjustmentIds().first()); + const QList adjustmentIds = _hyperion->getAdjustmentIds(); + if (adjustmentIds.isEmpty()) { + sendErrorReply("No adjustment data available", cmd); + return; + } + + const QString adjustmentId = adjustment["id"].toString(adjustmentIds.first()); ColorAdjustment *colorAdjustment = _hyperion->getAdjustment(adjustmentId); - if (colorAdjustment == nullptr) - { + if (colorAdjustment == nullptr) { Warning(_log, "Incorrect adjustment identifier: %s", adjustmentId.toStdString().c_str()); return; } - if (adjustment.contains("red")) - { - const QJsonArray &values = adjustment["red"].toArray(); - colorAdjustment->_rgbRedAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - - if (adjustment.contains("green")) - { - const QJsonArray &values = adjustment["green"].toArray(); - colorAdjustment->_rgbGreenAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - - if (adjustment.contains("blue")) - { - const QJsonArray &values = adjustment["blue"].toArray(); - colorAdjustment->_rgbBlueAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("cyan")) - { - const QJsonArray &values = adjustment["cyan"].toArray(); - colorAdjustment->_rgbCyanAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("magenta")) - { - const QJsonArray &values = adjustment["magenta"].toArray(); - colorAdjustment->_rgbMagentaAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("yellow")) - { - const QJsonArray &values = adjustment["yellow"].toArray(); - colorAdjustment->_rgbYellowAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("white")) - { - const QJsonArray &values = adjustment["white"].toArray(); - colorAdjustment->_rgbWhiteAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - - if (adjustment.contains("gammaRed")) - { - colorAdjustment->_rgbTransform.setGamma(adjustment["gammaRed"].toDouble(), colorAdjustment->_rgbTransform.getGammaG(), colorAdjustment->_rgbTransform.getGammaB()); - } - if (adjustment.contains("gammaGreen")) - { - colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), adjustment["gammaGreen"].toDouble(), colorAdjustment->_rgbTransform.getGammaB()); - } - if (adjustment.contains("gammaBlue")) - { - colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), colorAdjustment->_rgbTransform.getGammaG(), adjustment["gammaBlue"].toDouble()); - } - - if (adjustment.contains("backlightThreshold")) - { - colorAdjustment->_rgbTransform.setBacklightThreshold(adjustment["backlightThreshold"].toDouble()); - } - if (adjustment.contains("backlightColored")) - { - colorAdjustment->_rgbTransform.setBacklightColored(adjustment["backlightColored"].toBool()); - } - if (adjustment.contains("brightness")) - { - colorAdjustment->_rgbTransform.setBrightness(adjustment["brightness"].toInt()); - } - if (adjustment.contains("brightnessCompensation")) - { - colorAdjustment->_rgbTransform.setBrightnessCompensation(adjustment["brightnessCompensation"].toInt()); - } - - if (adjustment.contains("saturationGain")) - { - colorAdjustment->_okhsvTransform.setSaturationGain(adjustment["saturationGain"].toDouble()); - } - - if (adjustment.contains("brightnessGain")) - { - colorAdjustment->_okhsvTransform.setBrightnessGain(adjustment["brightnessGain"].toDouble()); - } - - // commit the changes + applyColorAdjustments(adjustment, colorAdjustment); + applyTransforms(adjustment, colorAdjustment); _hyperion->adjustmentsUpdated(); - - sendSuccessReply(command, tan); + sendSuccessReply(cmd); } -void JsonAPI::handleSourceSelectCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::applyColorAdjustments(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment) +{ + applyColorAdjustment("red", adjustment, colorAdjustment->_rgbRedAdjustment); + applyColorAdjustment("green", adjustment, colorAdjustment->_rgbGreenAdjustment); + applyColorAdjustment("blue", adjustment, colorAdjustment->_rgbBlueAdjustment); + applyColorAdjustment("cyan", adjustment, colorAdjustment->_rgbCyanAdjustment); + applyColorAdjustment("magenta", adjustment, colorAdjustment->_rgbMagentaAdjustment); + applyColorAdjustment("yellow", adjustment, colorAdjustment->_rgbYellowAdjustment); + applyColorAdjustment("white", adjustment, colorAdjustment->_rgbWhiteAdjustment); +} + +void JsonAPI::applyColorAdjustment(const QString &colorName, const QJsonObject &adjustment, RgbChannelAdjustment &rgbAdjustment) +{ + if (adjustment.contains(colorName)) { + const QJsonArray &values = adjustment[colorName].toArray(); + if (values.size() >= 3) { + rgbAdjustment.setAdjustment(static_cast(values[0U].toInt()), + static_cast(values[1U].toInt()), + static_cast(values[2U].toInt())); + } + } +} + +void JsonAPI::applyTransforms(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment) +{ + applyGammaTransform("gammaRed", adjustment, colorAdjustment->_rgbTransform, 'r'); + applyGammaTransform("gammaGreen", adjustment, colorAdjustment->_rgbTransform, 'g'); + applyGammaTransform("gammaBlue", adjustment, colorAdjustment->_rgbTransform, 'b'); + applyTransform("backlightThreshold", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBacklightThreshold); + applyTransform("backlightColored", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBacklightColored); + applyTransform("brightness", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBrightness); + applyTransform("brightnessCompensation", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBrightnessCompensation); + applyTransform("saturationGain", adjustment, colorAdjustment->_okhsvTransform, &OkhsvTransform::setSaturationGain); + applyTransform("brightnessGain", adjustment, colorAdjustment->_okhsvTransform, &OkhsvTransform::setBrightnessGain); +} + +void JsonAPI::applyGammaTransform(const QString &transformName, const QJsonObject &adjustment, RgbTransform &rgbTransform, char channel) +{ + if (adjustment.contains(transformName)) { + rgbTransform.setGamma(channel == 'r' ? adjustment[transformName].toDouble() : rgbTransform.getGammaR(), + channel == 'g' ? adjustment[transformName].toDouble() : rgbTransform.getGammaG(), + channel == 'b' ? adjustment[transformName].toDouble() : rgbTransform.getGammaB()); + } +} + +template +void JsonAPI::applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(bool)) +{ + if (adjustment.contains(transformName)) { + (transform.*setFunction)(adjustment[transformName].toBool()); + } +} + +template +void JsonAPI::applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(double)) +{ + if (adjustment.contains(transformName)) { + (transform.*setFunction)(adjustment[transformName].toDouble()); + } +} + +template +void JsonAPI::applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(uint8_t)) +{ + if (adjustment.contains(transformName)) { + (transform.*setFunction)(static_cast(adjustment[transformName].toInt())); + } +} + +void JsonAPI::handleSourceSelectCommand(const QJsonObject &message, const JsonApiCommand& cmd) { if (message.contains("auto")) { @@ -990,84 +609,63 @@ void JsonAPI::handleSourceSelectCommand(const QJsonObject &message, const QStrin } else { - sendErrorReply("Priority request is invalid", command, tan); + sendErrorReply("Priority request is invalid", cmd); return; } - sendSuccessReply(command, tan); + sendSuccessReply(cmd); } -void JsonAPI::handleConfigCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleConfigCommand(const QJsonObject& message, const JsonApiCommand& cmd) { - QString subcommand = message["subcommand"].toString(""); - QString full_command = command + "-" + subcommand; + switch (cmd.subCommand) { + case SubCommand::GetSchema: + handleSchemaGetCommand(message, cmd); + break; - if (subcommand == "getschema") - { - handleSchemaGetCommand(message, full_command, tan); - } - else if (subcommand == "getconfig") - { - if (_adminAuthorized) - sendSuccessDataReply(QJsonDocument(_hyperion->getQJsonConfig()), full_command, tan); - else - sendErrorReply("No Authorization", command, tan); - } - else if (subcommand == "setconfig") - { - if (_adminAuthorized) - handleConfigSetCommand(message, full_command, tan); - else - sendErrorReply("No Authorization", command, tan); - } - else if (subcommand == "restoreconfig") - { - if (_adminAuthorized) - handleConfigRestoreCommand(message, full_command, tan); - else - sendErrorReply("No Authorization", command, tan); - } - else if (subcommand == "reload") - { - if (_adminAuthorized) - { - Debug(_log, "Restarting due to RPC command"); - emit signalEvent(Event::Reload); + case SubCommand::GetConfig: + sendSuccessDataReply(_hyperion->getQJsonConfig(), cmd); + break; - sendSuccessReply(command + "-" + subcommand, tan); - } - else - { - sendErrorReply("No Authorization", command, tan); - } - } - else - { - sendErrorReply("unknown or missing subcommand", full_command, tan); + case SubCommand::SetConfig: + handleConfigSetCommand(message, cmd); + break; + + case SubCommand::RestoreConfig: + handleConfigRestoreCommand(message, cmd); + break; + + case SubCommand::Reload: + Debug(_log, "Restarting due to RPC command"); + emit signalEvent(Event::Reload); + sendSuccessReply(cmd); + break; + + default: + break; } } -void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const JsonApiCommand& cmd) { if (message.contains("config")) { QJsonObject config = message["config"].toObject(); if (API::isHyperionEnabled()) { - if ( API::saveSettings(config) ) - { - sendSuccessReply(command, tan); - } - else - { - sendErrorReply("Save settings failed", command, tan); + if ( API::saveSettings(config) ) { + sendSuccessReply(cmd); + } else { + sendErrorReply("Save settings failed", cmd); } } else - sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", command, tan); + { + sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", cmd); + } } } -void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const JsonApiCommand& cmd) { if (message.contains("config")) { @@ -1076,22 +674,26 @@ void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const QStri { if ( API::restoreSettings(config) ) { - sendSuccessReply(command, tan); + sendSuccessReply(cmd); } else { - sendErrorReply("Restore settings failed", command, tan); + sendErrorReply("Restore settings failed", cmd); } } else - sendErrorReply("Restoring configuration while Hyperion is disabled isn't possible", command, tan); + { + sendErrorReply("Restoring configuration while Hyperion is disabled is not possible", cmd); + } } } -void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const QString &command, int tan) +void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd) { // create result - QJsonObject schemaJson, alldevices, properties; + QJsonObject schemaJson; + QJsonObject alldevices; + QJsonObject properties; // make sure the resources are loaded (they may be left out after static linking) Q_INIT_RESOURCE(resource); @@ -1139,710 +741,507 @@ void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const QStri schemaJson.insert("properties", properties); // send the result - sendSuccessDataReply(QJsonDocument(schemaJson), command, tan); + sendSuccessDataReply(schemaJson, cmd); } -void JsonAPI::handleComponentStateCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleComponentStateCommand(const QJsonObject &message, const JsonApiCommand& cmd) { const QJsonObject &componentState = message["componentstate"].toObject(); QString comp = componentState["component"].toString("invalid"); bool compState = componentState["state"].toBool(true); QString replyMsg; - if (!API::setComponentState(comp, compState, replyMsg)) - { - sendErrorReply(replyMsg, command, tan); - return; + if (API::setComponentState(comp, compState, replyMsg)) { + sendSuccessReply(cmd); + } else { + sendErrorReply(replyMsg, cmd); } - sendSuccessReply(command, tan); } -void JsonAPI::streamLedColorsUpdate() +void JsonAPI::handleLedColorsCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd) { - emit streamLedcolorsUpdate(_currentLedValues); -} - -void JsonAPI::handleLedColorsCommand(const QJsonObject &message, const QString &command, int tan) -{ - // create result - QString subcommand = message["subcommand"].toString(""); - - // max 20 Hz (50ms) interval for streaming (default: 10 Hz (100ms)) - qint64 streaming_interval = qMax(message["interval"].toInt(100), 50); - - if (subcommand == "ledstream-start") - { - _streaming_leds_reply["success"] = true; - _streaming_leds_reply["command"] = command + "-ledstream-update"; - _streaming_leds_reply["tan"] = tan; - - connect(_hyperion, &Hyperion::rawLedColors, this, [=](const std::vector &ledValues) { - - if (ledValues != _currentLedValues) - { - _currentLedValues = ledValues; - if (!_ledStreamTimer->isActive() || _ledStreamTimer->interval() != streaming_interval) - { - _ledStreamTimer->start(streaming_interval); - } - } - else - { - _ledStreamTimer->stop(); - } - }); - + switch (cmd.subCommand) { + case SubCommand::LedStreamStart: + _jsonCB->subscribe( Subscription::LedColorsUpdate); + // TODO: Check if to be moved to CB // push once _hyperion->update(); - } - else if (subcommand == "ledstream-stop") - { - disconnect(_hyperion, &Hyperion::rawLedColors, this, 0); - _ledStreamTimer->stop(); - disconnect(_ledStreamConnection); - } - else if (subcommand == "imagestream-start") - { - _streaming_image_reply["success"] = true; - _streaming_image_reply["command"] = command + "-imagestream-update"; - _streaming_image_reply["tan"] = tan; + sendSuccessReply(cmd); + break; - connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection); - } - else if (subcommand == "imagestream-stop") - { - disconnect(_hyperion, &Hyperion::currentImage, this, 0); - } - else - { - return; - } + case SubCommand::LedStreamStop: + _jsonCB->unsubscribe( Subscription::LedColorsUpdate); + sendSuccessReply(cmd); + break; - sendSuccessReply(command + "-" + subcommand, tan); + case SubCommand::ImageStreamStart: + _jsonCB->subscribe(Subscription::ImageUpdate); + sendSuccessReply(cmd); + break; + + case SubCommand::ImageStreamStop: + _jsonCB->unsubscribe(Subscription::ImageUpdate); + sendSuccessReply(cmd); + break; + + default: + break; + } } -void JsonAPI::handleLoggingCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleLoggingCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd) { - // create result - QString subcommand = message["subcommand"].toString(""); + switch (cmd.subCommand) { + case SubCommand::Start: + _jsonCB->subscribe("logmsg-update"); + sendSuccessReply(cmd); + break; - if (API::isAdminAuthorized()) - { - _streaming_logging_reply["success"] = true; - _streaming_logging_reply["command"] = command; - _streaming_logging_reply["tan"] = tan; - - if (subcommand == "start") - { - if (!_streaming_logging_activated) - { - _streaming_logging_reply["command"] = command + "-update"; - connect(LoggerManager::getInstance().data(), &LoggerManager::newLogMessage, this, &JsonAPI::incommingLogMessage); - - emit incommingLogMessage (Logger::T_LOG_MESSAGE{}); // needed to trigger log sending - Debug(_log, "log streaming activated for client %s", _peerAddress.toStdString().c_str()); - } - } - else if (subcommand == "stop") - { - if (_streaming_logging_activated) - { - disconnect(LoggerManager::getInstance().data(), &LoggerManager::newLogMessage, this, &JsonAPI::incommingLogMessage); - _streaming_logging_activated = false; - Debug(_log, "log streaming deactivated for client %s", _peerAddress.toStdString().c_str()); - } - } - else - { - return; - } - - sendSuccessReply(command + "-" + subcommand, tan); - } - else - { - sendErrorReply("No Authorization", command + "-" + subcommand, tan); + case SubCommand::Stop: + _jsonCB->unsubscribe("logmsg-update"); + sendSuccessReply(cmd); + break; + default: + break; } } -void JsonAPI::handleProcessingCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleProcessingCommand(const QJsonObject &message, const JsonApiCommand& cmd) { API::setLedMappingType(ImageProcessor::mappingTypeToInt(message["mappingType"].toString("multicolor_mean"))); - sendSuccessReply(command, tan); + sendSuccessReply(cmd); } -void JsonAPI::handleVideoModeCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleVideoModeCommand(const QJsonObject &message, const JsonApiCommand& cmd) { API::setVideoMode(parse3DMode(message["videoMode"].toString("2D"))); - sendSuccessReply(command, tan); + sendSuccessReply(cmd); } -void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const JsonApiCommand& cmd) +{ + switch (cmd.subCommand) { + case SubCommand::TokenRequired: + handleTokenRequired(cmd); + break; + case SubCommand::AdminRequired: + handleAdminRequired(cmd); + break; + case SubCommand::NewPasswordRequired: + handleNewPasswordRequired(cmd); + break; + case SubCommand::Logout: + handleLogout(cmd); + break; + case SubCommand::NewPassword: + handleNewPassword(message, cmd); + break; + case SubCommand::CreateToken: + handleCreateToken(message, cmd); + break; + case SubCommand::RenameToken: + handleRenameToken(message, cmd); + break; + case SubCommand::DeleteToken: + handleDeleteToken(message, cmd); + break; + case SubCommand::RequestToken: + handleRequestToken(message, cmd); + break; + case SubCommand::GetPendingTokenRequests: + handleGetPendingTokenRequests(cmd); + break; + case SubCommand::AnswerRequest: + handleAnswerRequest(message, cmd); + break; + case SubCommand::GetTokenList: + handleGetTokenList(cmd); + break; + case SubCommand::Login: + handleLogin(message, cmd); + break; + default: + return; + } +} + +void JsonAPI::handleTokenRequired(const JsonApiCommand& cmd) +{ + bool isTokenRequired = !islocalConnection() || _authManager->isLocalAuthRequired(); + QJsonObject response { { "required", isTokenRequired} }; + sendSuccessDataReply(response, cmd); +} + +void JsonAPI::handleAdminRequired(const JsonApiCommand& cmd) +{ + bool isAdminAuthRequired = true; + QJsonObject response { { "adminRequired", isAdminAuthRequired} }; + sendSuccessDataReply(response, cmd); +} + +void JsonAPI::handleNewPasswordRequired(const JsonApiCommand& cmd) +{ + QJsonObject response { { "newPasswordRequired", API::hasHyperionDefaultPw() } }; + sendSuccessDataReply(response, cmd); +} + +void JsonAPI::handleLogout(const JsonApiCommand& cmd) +{ + API::logout(); + sendSuccessReply(cmd); +} + +void JsonAPI::handleNewPassword(const QJsonObject &message, const JsonApiCommand& cmd) +{ + const QString password = message["password"].toString().trimmed(); + const QString newPassword = message["newPassword"].toString().trimmed(); + if (API::updateHyperionPassword(password, newPassword)) { + sendSuccessReply(cmd); + } else { + sendErrorReply("Failed to update user password", cmd); + } +} + +void JsonAPI::handleCreateToken(const QJsonObject &message, const JsonApiCommand& cmd) { - Debug(_log,""); - const QString &subc = message["subcommand"].toString().trimmed(); - const QString &id = message["id"].toString().trimmed(); - const QString &password = message["password"].toString().trimmed(); - const QString &newPassword = message["newPassword"].toString().trimmed(); const QString &comment = message["comment"].toString().trimmed(); + AuthManager::AuthDefinition def; + const QString createTokenResult = API::createToken(comment, def); + if (createTokenResult.isEmpty()) { + QJsonObject newTok; + newTok["comment"] = def.comment; + newTok["id"] = def.id; + newTok["token"] = def.token; - // catch test if auth is required - if (subc == "tokenRequired") - { - QJsonObject req; - req["required"] = !API::isAuthorized(); - - sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan); - return; + sendSuccessDataReply(newTok, cmd); + } else { + sendErrorReply("Token creation failed", {createTokenResult}, cmd); } +} - // catch test if admin auth is required - if (subc == "adminRequired") - { - Debug(_log,"adminRequired: [%d]", !API::isAdminAuthorized()); - QJsonObject req; - req["adminRequired"] = !API::isAdminAuthorized(); - sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan); - return; +void JsonAPI::handleRenameToken(const QJsonObject &message, const JsonApiCommand& cmd) +{ + const QString &identifier = message["id"].toString().trimmed(); + const QString &comment = message["comment"].toString().trimmed(); + const QString renameTokenResult = API::renameToken(identifier, comment); + if (renameTokenResult.isEmpty()) { + sendSuccessReply(cmd); + } else { + sendErrorReply("Token rename failed", {renameTokenResult}, cmd); } +} - // default hyperion password is a security risk, replace it asap - if (subc == "newPasswordRequired") - { - QJsonObject req; - req["newPasswordRequired"] = API::hasHyperionDefaultPw(); - sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan); - return; +void JsonAPI::handleDeleteToken(const QJsonObject &message, const JsonApiCommand& cmd) +{ + const QString &identifier = message["id"].toString().trimmed(); + const QString deleteTokenResult = API::deleteToken(identifier); + if (deleteTokenResult.isEmpty()) { + sendSuccessReply(cmd); + } else { + sendErrorReply("Token deletion failed", {deleteTokenResult}, cmd); } +} - // catch logout - if (!_noListener && subc == "logout") - { - // disconnect all kind of data callbacks - JsonAPI::stopDataConnections(); // TODO move to API - API::logout(); - sendSuccessReply(command + "-" + subc, tan); - return; - } - - // change password - if (subc == "newPassword") - { - // use password, newPassword - if (API::isAdminAuthorized()) - { - if (API::updateHyperionPassword(password, newPassword)) - { - sendSuccessReply(command + "-" + subc, tan); - return; - } - sendErrorReply("Failed to update user password", command + "-" + subc, tan); - return; - } - sendErrorReply("No Authorization", command + "-" + subc, tan); - return; - } - - // token created from ui - if (!_noListener && subc == "createToken") - { - // use comment - // for user authorized sessions - AuthManager::AuthDefinition def; - const QString createTokenResult = API::createToken(comment, def); - if (createTokenResult.isEmpty()) - { - QJsonObject newTok; - newTok["comment"] = def.comment; - newTok["id"] = def.id; - newTok["token"] = def.token; - - sendSuccessDataReply(QJsonDocument(newTok), command + "-" + subc, tan); - return; - } - sendErrorReply(createTokenResult, command + "-" + subc, tan); - return; - } - - // rename Token - if (subc == "renameToken") - { - // use id/comment - const QString renameTokenResult = API::renameToken(id, comment); - if (renameTokenResult.isEmpty()) - { - sendSuccessReply(command + "-" + subc, tan); - return; - } - sendErrorReply(renameTokenResult, command + "-" + subc, tan); - return; - } - - // delete token - if (subc == "deleteToken") - { - // use id - const QString deleteTokenResult = API::deleteToken(id); - if (deleteTokenResult.isEmpty()) - { - sendSuccessReply(command + "-" + subc, tan); - return; - } - sendErrorReply(deleteTokenResult, command + "-" + subc, tan); - return; - } - - // catch token request - if (!_noListener && subc == "requestToken") - { - // use id/comment - const bool &acc = message["accept"].toBool(true); - if (acc) - API::setNewTokenRequest(comment, id, tan); - else - API::cancelNewTokenRequest(comment, id); +void JsonAPI::handleRequestToken(const QJsonObject &message, const JsonApiCommand& cmd) +{ + const QString &identifier = message["id"].toString().trimmed(); + const QString &comment = message["comment"].toString().trimmed(); + const bool &acc = message["accept"].toBool(true); + if (acc) { + API::setNewTokenRequest(comment, identifier, cmd.tan); + } else { + API::cancelNewTokenRequest(comment, identifier); // client should wait for answer - return; } +} - // get pending token requests - if (!_noListener && subc == "getPendingTokenRequests") - { - QVector vec; - if (API::getPendingTokenRequests(vec)) +void JsonAPI::handleGetPendingTokenRequests(const JsonApiCommand& cmd) +{ + QVector vec; + if (API::getPendingTokenRequests(vec)) { + QJsonArray pendingTokeRequests; + for (const auto &entry : std::as_const(vec)) { - QJsonArray arr; - for (const auto &entry : std::as_const(vec)) - { - QJsonObject obj; - obj["comment"] = entry.comment; - obj["id"] = entry.id; - obj["timeout"] = int(entry.timeoutTime); - arr.append(obj); - } - sendSuccessDataReply(QJsonDocument(arr), command + "-" + subc, tan); + QJsonObject obj; + obj["comment"] = entry.comment; + obj["id"] = entry.id; + obj["timeout"] = int(entry.timeoutTime); + obj["tan"] = entry.tan; + pendingTokeRequests.append(obj); } - else + sendSuccessDataReply(pendingTokeRequests, cmd); + } +} + +void JsonAPI::handleAnswerRequest(const QJsonObject &message, const JsonApiCommand& cmd) +{ + const QString &identifier = message["id"].toString().trimmed(); + const bool &accept = message["accept"].toBool(false); + if (API::handlePendingTokenRequest(identifier, accept)) { + sendSuccessReply(cmd); + } else { + sendErrorReply("Unable to handle token acceptance or denial", cmd); + } +} + +void JsonAPI::handleGetTokenList(const JsonApiCommand& cmd) +{ + QVector defVect; + if (API::getTokenList(defVect)) + { + QJsonArray tokenList; + for (const auto &entry : std::as_const(defVect)) { - sendErrorReply("No Authorization", command + "-" + subc, tan); + QJsonObject token; + token["comment"] = entry.comment; + token["id"] = entry.id; + token["last_use"] = entry.lastUse; + + tokenList.append(token); } - - return; + sendSuccessDataReply(tokenList, cmd); } +} - // accept/deny token request - if (!_noListener && subc == "answerRequest") +void JsonAPI::handleLogin(const QJsonObject &message, const JsonApiCommand& cmd) +{ + const QString &token = message["token"].toString().trimmed(); + if (!token.isEmpty()) { - // use id - const bool &accept = message["accept"].toBool(false); - if (!API::handlePendingTokenRequest(id, accept)) - sendErrorReply("No Authorization", command + "-" + subc, tan); - return; - } - - // get token list - if (subc == "getTokenList") - { - QVector defVect; - if (API::getTokenList(defVect)) + // userToken is longer than app token + if (token.size() > APP_TOKEN_LENGTH) { - QJsonArray tArr; - for (const auto &entry : qAsConst(defVect)) - { - QJsonObject subO; - subO["comment"] = entry.comment; - subO["id"] = entry.id; - subO["last_use"] = entry.lastUse; - - tArr.append(subO); + if (API::isUserTokenAuthorized(token)) { + sendSuccessReply(cmd); + } else { + sendNoAuthorization(cmd); } - sendSuccessDataReply(QJsonDocument(tArr), command + "-" + subc, tan); - return; - } - sendErrorReply("No Authorization", command + "-" + subc, tan); - return; - } - // login - if (!_noListener && subc == "login") - { - const QString &token = message["token"].toString().trimmed(); - - qDebug() << "token: len: " << token.count() << " [" << token; - qDebug() << "password: len: " << password.count() << " [" << password; - - // catch token - if (!token.isEmpty()) - { - // userToken is longer - if (token.size() > 36) - { - Debug(_log,"isUserTokenAuthorized [%d]", API::isUserTokenAuthorized(token)); - if (API::isUserTokenAuthorized(token)) - sendSuccessReply(command + "-" + subc, tan); - else - sendErrorReply("No Authorization", command + "-" + subc, tan); - - return; - } - // usual app token is 36 - if (token.size() == 36) - { - Debug(_log,"isTokenAuthorized [%d]", API::isTokenAuthorized(token)); - if (API::isTokenAuthorized(token)) - { - sendSuccessReply(command + "-" + subc, tan); - } - else - sendErrorReply("No Authorization", command + "-" + subc, tan); - } - std::cout << "handleAuthorizeCommand [login] - _authorized [" << _authorized << "] _adminAuthorized [" << _adminAuthorized << "]" << std::endl; return; } - // password - // use password - if (password.size() >= 8) + if (token.size() == APP_TOKEN_LENGTH) { - Debug(_log,"password: isUserAuthorized [%d] ", API::isUserAuthorized(token)); - QString userTokenRep; - if (API::isUserAuthorized(password) && API::getUserToken(userTokenRep)) - { - // Return the current valid Hyperion user token - QJsonObject obj; - obj["token"] = userTokenRep; - sendSuccessDataReply(QJsonDocument(obj), command + "-" + subc, tan); + if (API::isTokenAuthorized(token)) { + sendSuccessReply(cmd); + } else { + sendNoAuthorization(cmd); } - else - { - sendErrorReply("No Authorization", command + "-" + subc, tan); - } - } - else - { - sendErrorReply("Password too short", command + "-" + subc, tan); } return; } - if(_noListener) + // password + const QString &password = message["password"].toString().trimmed(); + if (password.size() >= MIN_PASSWORD_LENGTH) { - sendErrorReply("Command not supported via single API calls using HTTP/S", command + "-" + subc, tan); + QString userTokenRep; + if (API::isUserAuthorized(password) && API::getUserToken(userTokenRep)) + { + // Return the current valid Hyperion user token + QJsonObject response { { "token", userTokenRep } }; + sendSuccessDataReply(response, cmd); + } + else + { + sendNoAuthorization(cmd); + } } else { - handleNotImplemented(command, tan); + sendErrorReply(QString("Password is too short. Minimum length: %1 characters").arg(MIN_PASSWORD_LENGTH), cmd); } } -void JsonAPI::handleInstanceCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::issueNewPendingTokenRequest(const QString &identifier, const QString &comment) { - const QString &subc = message["subcommand"].toString(); + QJsonObject tokenRequest; + tokenRequest["comment"] = comment; + tokenRequest["id"] = identifier; + tokenRequest["timeout"] = static_cast(NEW_TOKEN_REQUEST_TIMEOUT.count()); + + sendNewRequest(tokenRequest, "authorize-tokenRequest"); +} + +void JsonAPI::handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &identifier, const int &tan) +{ + const QString cmd = "authorize-requestToken"; + QJsonObject result; + result["token"] = token; + result["comment"] = comment; + result["id"] = identifier; + + if (success) { + sendSuccessDataReply(result, cmd, tan); + } else { + sendErrorReply("Token request timeout or denied", {}, cmd, tan); + } +} + +void JsonAPI::handleInstanceCommand(const QJsonObject &message, const JsonApiCommand& cmd) +{ + QString replyMsg; + const quint8 &inst = static_cast(message["instance"].toInt()); const QString &name = message["name"].toString(); - if (subc == "switchTo") - { + switch (cmd.subCommand) { + case SubCommand::SwitchTo: if (handleInstanceSwitch(inst)) { - QJsonObject obj; - obj["instance"] = inst; - sendSuccessDataReply(QJsonDocument(obj), command + "-" + subc, tan); + QJsonObject response { { "instance", inst } }; + sendSuccessDataReply(response, cmd); } else - sendErrorReply("Selected Hyperion instance isn't running", command + "-" + subc, tan); - return; - } - - if (subc == "startInstance") - { - //Only send update once - weakConnect(this, &API::onStartInstanceResponse, [this, command, subc] (int tan) { - sendSuccessReply(command + "-" + subc, tan); + sendErrorReply("Selected Hyperion instance is not running", cmd); + } + break; + + case SubCommand::StartInstance: + //Only send update once + weakConnect(this, &API::onStartInstanceResponse, [this, cmd] () + { + sendSuccessReply(cmd); }); - if (!API::startInstance(inst, tan)) - sendErrorReply("Can't start Hyperion instance index " + QString::number(inst), command + "-" + subc, tan); - - return; - } - - if (subc == "stopInstance") - { + if (!API::startInstance(inst, cmd.tan)) + { + sendErrorReply("Cannot start Hyperion instance index " + QString::number(inst), cmd); + } + break; + case SubCommand::StopInstance: // silent fail API::stopInstance(inst); - sendSuccessReply(command + "-" + subc, tan); - return; - } + sendSuccessReply(cmd); + break; - if (subc == "deleteInstance") - { - QString replyMsg; + case SubCommand::DeleteInstance: + handleConfigRestoreCommand(message, cmd); if (API::deleteInstance(inst, replyMsg)) - sendSuccessReply(command + "-" + subc, tan); + { + sendSuccessReply(cmd); + } else - sendErrorReply(replyMsg, command + "-" + subc, tan); - return; - } + { + sendErrorReply(replyMsg, cmd); + } + break; - // create and save name requires name - if (name.isEmpty()) - sendErrorReply("Name string required for this command", command + "-" + subc, tan); + case SubCommand::CreateInstance: + case SubCommand::SaveName: + // create and save name requires name + if (name.isEmpty()) { + sendErrorReply("Name string required for this command", cmd); + return; + } - if (subc == "createInstance") - { - QString replyMsg = API::createInstance(name); - if (replyMsg.isEmpty()) - sendSuccessReply(command + "-" + subc, tan); - else - sendErrorReply(replyMsg, command + "-" + subc, tan); - return; - } + if (cmd.subCommand == SubCommand::CreateInstance) { + replyMsg = API::createInstance(name); + } else { + replyMsg = setInstanceName(inst, name); + } - if (subc == "saveName") - { - QString replyMsg = API::setInstanceName(inst, name); - if (replyMsg.isEmpty()) - sendSuccessReply(command + "-" + subc, tan); - else - sendErrorReply(replyMsg, command + "-" + subc, tan); - return; + if (replyMsg.isEmpty()) { + sendSuccessReply(cmd); + } else { + sendErrorReply(replyMsg, cmd); + } + break; + default: + break; } } -void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const JsonApiCommand& cmd) { - Debug(_log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData() ); - - const QString &subc = message["subcommand"].toString().trimmed(); const QString &devType = message["ledDeviceType"].toString().trimmed(); + const LedDeviceRegistry& ledDevices = LedDeviceWrapper::getDeviceMap(); - QString full_command = command + "-" + subc; - - // TODO: Validate that device type is a valid one - - { - QJsonObject config; - config.insert("type", devType); - LedDevice* ledDevice = nullptr; - - if (subc == "discover") - { - ledDevice = LedDeviceFactory::construct(config); - const QJsonObject ¶ms = message["params"].toObject(); - const QJsonObject devicesDiscovered = ledDevice->discover(params); - - Debug(_log, "response: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() ); - - sendSuccessDataReply(QJsonDocument(devicesDiscovered), full_command, tan); - } - else if (subc == "getProperties") - { - ledDevice = LedDeviceFactory::construct(config); - const QJsonObject ¶ms = message["params"].toObject(); - const QJsonObject deviceProperties = ledDevice->getProperties(params); - - Debug(_log, "response: [%s]", QString(QJsonDocument(deviceProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); - - sendSuccessDataReply(QJsonDocument(deviceProperties), full_command, tan); - } - else if (subc == "identify") - { - ledDevice = LedDeviceFactory::construct(config); - const QJsonObject ¶ms = message["params"].toObject(); - ledDevice->identify(params); - - sendSuccessReply(full_command, tan); - } - else if (subc == "addAuthorization") - { - ledDevice = LedDeviceFactory::construct(config); - const QJsonObject& params = message["params"].toObject(); - const QJsonObject response = ledDevice->addAuthorization(params); - - Debug(_log, "response: [%s]", QString(QJsonDocument(response).toJson(QJsonDocument::Compact)).toUtf8().constData()); - - sendSuccessDataReply(QJsonDocument(response), full_command, tan); - } - else - { - sendErrorReply("Unknown or missing subcommand", full_command, tan); - } - - delete ledDevice; + if (ledDevices.count(devType) == 0) { + sendErrorReply(QString("Unknown LED-Device type: %1").arg(devType), cmd); + return; } + + QJsonObject config { { "type", devType } }; + LedDevice* ledDevice = LedDeviceFactory::construct(config); + + switch (cmd.subCommand) { + case SubCommand::Discover: + handleLedDeviceDiscover(*ledDevice, message, cmd); + break; + case SubCommand::GetProperties: + handleLedDeviceGetProperties(*ledDevice, message, cmd); + break; + case SubCommand::Identify: + handleLedDeviceIdentify(*ledDevice, message, cmd); + break; + case SubCommand::AddAuthorization: + handleLedDeviceAddAuthorization(*ledDevice, message, cmd); + break; + default: + break; + } + + delete ledDevice; } -void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const QString& command, int tan) +void JsonAPI::handleLedDeviceDiscover(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd) { - DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData()); + const QJsonObject ¶ms = message["params"].toObject(); + const QJsonObject devicesDiscovered = ledDevice.discover(params); + Debug(_log, "response: [%s]", QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact).constData() ); + sendSuccessDataReply(devicesDiscovered, cmd); +} - const QString& subc = message["subcommand"].toString().trimmed(); +void JsonAPI::handleLedDeviceGetProperties(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd) +{ + const QJsonObject ¶ms = message["params"].toObject(); + const QJsonObject deviceProperties = ledDevice.getProperties(params); + Debug(_log, "response: [%s]", QJsonDocument(deviceProperties).toJson(QJsonDocument::Compact).constData() ); + sendSuccessDataReply(deviceProperties, cmd); +} + +void JsonAPI::handleLedDeviceIdentify(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd) +{ + const QJsonObject ¶ms = message["params"].toObject(); + ledDevice.identify(params); + sendSuccessReply(cmd); +} + +void JsonAPI::handleLedDeviceAddAuthorization(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd) +{ + const QJsonObject& params = message["params"].toObject(); + const QJsonObject response = ledDevice.addAuthorization(params); + sendSuccessDataReply(response, cmd); +} + +void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const JsonApiCommand& cmd) { const QString& sourceType = message["sourceType"].toString().trimmed(); + const QStringList sourceTypes {"screen", "video", "audio"}; - QString full_command = command + "-" + subc; + if (!sourceTypes.contains(sourceType)) { + sendErrorReply(QString("Unknown input source type: %1").arg(sourceType), cmd); + return; + } - // TODO: Validate that source type is a valid one - { - if (subc == "discover") - { - QJsonObject inputSourcesDiscovered; - inputSourcesDiscovered.insert("sourceType", sourceType); - QJsonArray videoInputs; - QJsonArray audioInputs; + if (cmd.subCommand == SubCommand::Discover) { -#if defined(ENABLE_V4L2) || defined(ENABLE_MF) + const QJsonObject& params = message["params"].toObject(); + QJsonObject inputSourcesDiscovered = JsonInfo().discoverSources(sourceType, params); - if (sourceType == "video" ) - { -#if defined(ENABLE_MF) - MFGrabber* grabber = new MFGrabber(); -#elif defined(ENABLE_V4L2) - V4L2Grabber* grabber = new V4L2Grabber(); -#endif - QJsonObject params; - videoInputs = grabber->discover(params); - delete grabber; - } - else -#endif + DebugIf(verbose, _log, "response: [%s]", QJsonDocument(inputSourcesDiscovered).toJson(QJsonDocument::Compact).constData()); -#if defined(ENABLE_AUDIO) - if (sourceType == "audio") - { - AudioGrabber* grabber; -#ifdef WIN32 - grabber = new AudioGrabberWindows(); -#endif - -#ifdef __linux__ - grabber = new AudioGrabberLinux(); -#endif - QJsonObject params; - audioInputs = grabber->discover(params); - delete grabber; - } - else -#endif - { - DebugIf(verbose, _log, "sourceType: [%s]", QSTRING_CSTR(sourceType)); - - if (sourceType == "screen") - { - QJsonObject params; - - QJsonObject device; - #ifdef ENABLE_QT - QScopedPointer qtgrabber(new QtGrabber()); - device = qtgrabber->discover(params); - if (!device.isEmpty() ) - { - videoInputs.append(device); - } - #endif - - #ifdef ENABLE_DX - QScopedPointer dxgrabber (new DirectXGrabber()); - device = dxgrabber->discover(params); - if (!device.isEmpty() ) - { - videoInputs.append(device); - } - #endif - - #ifdef ENABLE_X11 - QScopedPointer x11Grabber(new X11Grabber()); - device = x11Grabber->discover(params); - if (!device.isEmpty() ) - { - videoInputs.append(device); - } - #endif - - #ifdef ENABLE_XCB - QScopedPointer xcbGrabber (new XcbGrabber()); - device = xcbGrabber->discover(params); - if (!device.isEmpty() ) - { - videoInputs.append(device); - } - #endif - - //Ignore FB for Amlogic, as it is embedded in the Amlogic grabber itself - #if defined(ENABLE_FB) && !defined(ENABLE_AMLOGIC) - - QScopedPointer fbGrabber(new FramebufferFrameGrabber()); - device = fbGrabber->discover(params); - if (!device.isEmpty() ) - { - videoInputs.append(device); - } - #endif - - #if defined(ENABLE_DISPMANX) - QScopedPointer dispmanx(new DispmanxFrameGrabber()); - if (dispmanx->isAvailable()) - { - device = dispmanx->discover(params); - if (!device.isEmpty() ) - { - videoInputs.append(device); - } - } - #endif - - #if defined(ENABLE_AMLOGIC) - QScopedPointer amlGrabber(new AmlogicGrabber()); - device = amlGrabber->discover(params); - if (!device.isEmpty() ) - { - videoInputs.append(device); - } - #endif - - #if defined(ENABLE_OSX) - QScopedPointer osxGrabber(new OsxFrameGrabber()); - device = osxGrabber->discover(params); - if (!device.isEmpty() ) - { - videoInputs.append(device); - } - #endif - } - - } - inputSourcesDiscovered["video_sources"] = videoInputs; - inputSourcesDiscovered["audio_sources"] = audioInputs; - - DebugIf(verbose, _log, "response: [%s]", QString(QJsonDocument(inputSourcesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); - - sendSuccessDataReply(QJsonDocument(inputSourcesDiscovered), full_command, tan); - } - else - { - sendErrorReply("Unknown or missing subcommand", full_command, tan); - } + sendSuccessDataReply(inputSourcesDiscovered, cmd); } } -void JsonAPI::handleServiceCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleServiceCommand(const QJsonObject &message, const JsonApiCommand& cmd) { - DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData()); - - const QString &subc = message["subcommand"].toString().trimmed(); - const QString type = message["serviceType"].toString().trimmed(); - - QString full_command = command + "-" + subc; - - if (subc == "discover") + if (cmd.subCommand == SubCommand::Discover) { QByteArray serviceType; - - QJsonObject servicesDiscovered; - QJsonObject servicesOfType; - QJsonArray serviceList; - + const QString type = message["serviceType"].toString().trimmed(); #ifdef ENABLE_MDNS QString discoveryMethod("mDNS"); serviceType = MdnsServiceRegister::getServiceType(type); @@ -1851,81 +1250,60 @@ void JsonAPI::handleServiceCommand(const QJsonObject &message, const QString &co #endif if (!serviceType.isEmpty()) { + QJsonArray serviceList; #ifdef ENABLE_MDNS QMetaObject::invokeMethod(MdnsBrowser::getInstance().data(), "browseForServiceType", - Qt::QueuedConnection, Q_ARG(QByteArray, serviceType)); + Qt::QueuedConnection, Q_ARG(QByteArray, serviceType)); serviceList = MdnsBrowser::getInstance().data()->getServicesDiscoveredJson(serviceType, MdnsServiceRegister::getServiceNameFilter(type), DEFAULT_DISCOVER_TIMEOUT); #endif + QJsonObject servicesDiscovered; + QJsonObject servicesOfType; + servicesOfType.insert(type, serviceList); servicesDiscovered.insert("discoveryMethod", discoveryMethod); servicesDiscovered.insert("services", servicesOfType); - sendSuccessDataReply(QJsonDocument(servicesDiscovered), full_command, tan); + sendSuccessDataReply(servicesDiscovered, cmd); } else { - sendErrorReply(QString("Discovery of service type [%1] via %2 not supported").arg(type, discoveryMethod), full_command, tan); + sendErrorReply(QString("Discovery of service type [%1] via %2 not supported").arg(type, discoveryMethod), cmd); } } - else - { - sendErrorReply("Unknown or missing subcommand", full_command, tan); - } } -void JsonAPI::handleSystemCommand(const QJsonObject &message, const QString &command, int tan) +void JsonAPI::handleSystemCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd) { - DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData()); - - const QString &subc = message["subcommand"].toString().trimmed(); - - if (subc == "suspend") - { + switch (cmd.subCommand) { + case SubCommand::Suspend: emit signalEvent(Event::Suspend); - sendSuccessReply(command + "-" + subc, tan); - } - else if (subc == "resume") - { + break; + case SubCommand::Resume: emit signalEvent(Event::Resume); - sendSuccessReply(command + "-" + subc, tan); - } - else if (subc == "restart") - { + break; + case SubCommand::Restart: emit signalEvent(Event::Restart); - sendSuccessReply(command + "-" + subc, tan); - } - else if (subc == "toggleSuspend") - { + break; + case SubCommand::ToggleSuspend: emit signalEvent(Event::ToggleSuspend); - sendSuccessReply(command + "-" + subc, tan); - } - else if (subc == "idle") - { + break; + case SubCommand::Idle: emit signalEvent(Event::Idle); - sendSuccessReply(command + "-" + subc, tan); - } - else if (subc == "resumeIdle") - { - emit signalEvent(Event::ResumeIdle); - sendSuccessReply(command + "-" + subc, tan); - } - else if (subc == "toggleIdle") - { + break; + case SubCommand::ToggleIdle: emit signalEvent(Event::ToggleIdle); - sendSuccessReply(command + "-" + subc, tan); - } - else - { - QString full_command = command + "-" + subc; - sendErrorReply("Unknown or missing subcommand", full_command, tan); + break; + default: + return; } + sendSuccessReply(cmd); } -void JsonAPI::handleNotImplemented(const QString &command, int tan) +void JsonAPI::sendSuccessReply(const JsonApiCommand& cmd) { - sendErrorReply("Command not implemented", command, tan); + sendSuccessReply(cmd.toString(), cmd.tan); } void JsonAPI::sendSuccessReply(const QString &command, int tan) @@ -1941,123 +1319,99 @@ void JsonAPI::sendSuccessReply(const QString &command, int tan) emit callbackMessage(reply); } -void JsonAPI::sendSuccessDataReply(const QJsonDocument &doc, const QString &command, int tan) +void JsonAPI::sendSuccessDataReply(const QJsonValue &infoData, const JsonApiCommand& cmd) +{ + sendSuccessDataReplyWithError(infoData, cmd.toString(), cmd.tan, {}); +} + +void JsonAPI::sendSuccessDataReply(const QJsonValue &infoData, const QString &command, int tan) +{ + sendSuccessDataReplyWithError(infoData, command, tan, {}); +} + +void JsonAPI::sendSuccessDataReplyWithError(const QJsonValue &infoData, const JsonApiCommand& cmd, const QStringList& errorDetails) +{ + sendSuccessDataReplyWithError(infoData, cmd.toString(), cmd.tan, errorDetails); +} + +void JsonAPI::sendSuccessDataReplyWithError(const QJsonValue &infoData, const QString &command, int tan, const QStringList& errorDetails) { QJsonObject reply; reply["instance"] = _hyperion->getInstanceIndex(); reply["success"] = true; + reply["command"] = command; reply["tan"] = tan; - if (doc.isArray()) - reply["info"] = doc.array(); - else - reply["info"] = doc.object(); + reply["info"] = infoData; + + if (!errorDetails.isEmpty()) + { + QJsonArray errorsArray; + for (const QString& errorString : errorDetails) { + QJsonObject errorObject; + errorObject["description"] = errorString; + errorsArray.append(errorObject); + } + reply["errorData"] = errorsArray; + } emit callbackMessage(reply); } -void JsonAPI::sendErrorReply(const QString &error, const QString &command, int tan) +void JsonAPI::sendNewRequest(const QJsonValue &infoData, const JsonApiCommand& cmd) +{ + sendSuccessDataReplyWithError(infoData, cmd.toString()); +} + +void JsonAPI::sendNewRequest(const QJsonValue &infoData, const QString &command) +{ + QJsonObject request; + request["command"] = command; + request["info"] = infoData; + + emit callbackMessage(request); +} + +void JsonAPI::sendErrorReply(const QString &error, const JsonApiCommand& cmd) +{ + sendErrorReply(error, {}, cmd.toString(), cmd.tan); +} + +void JsonAPI::sendErrorReply(const QString &error, const QStringList& errorDetails, const JsonApiCommand& cmd) +{ + sendErrorReply(error, errorDetails, cmd.toString(), cmd.tan); +} + +void JsonAPI::sendErrorReply(const QString &error, const QStringList& errorDetails, const QString &command, int tan) { // create reply QJsonObject reply; reply["instance"] = _hyperion->getInstanceIndex(); reply["success"] = false; - reply["error"] = error; + reply["command"] = command; reply["tan"] = tan; - // send reply + reply["error"] = error; + if (!errorDetails.isEmpty()) + { + QJsonArray errorsArray; + for (const QString& errorString : errorDetails) { + QJsonObject errorObject; + errorObject["description"] = errorString; + errorsArray.append(errorObject); + } + reply["errorData"] = errorsArray; + } + emit callbackMessage(reply); } -void JsonAPI::streamLedcolorsUpdate(const std::vector &ledColors) +void JsonAPI::sendNoAuthorization(const JsonApiCommand& cmd) { - QJsonObject result; - QJsonArray leds; - - for (const auto &color : ledColors) - { - leds << QJsonValue(color.red) << QJsonValue(color.green) << QJsonValue(color.blue); - } - - result["leds"] = leds; - _streaming_leds_reply["result"] = result; - - // send the result - emit callbackMessage(_streaming_leds_reply); + sendErrorReply(NO_AUTHORIZATION, cmd); } -void JsonAPI::setImage(const Image &image) -{ - QImage jpgImage((const uint8_t *)image.memptr(), image.width(), image.height(), 3 * image.width(), QImage::Format_RGB888); - QByteArray ba; - QBuffer buffer(&ba); - buffer.open(QIODevice::WriteOnly); - jpgImage.save(&buffer, "jpg"); - - QJsonObject result; - result["image"] = "data:image/jpg;base64," + QString(ba.toBase64()); - _streaming_image_reply["result"] = result; - emit callbackMessage(_streaming_image_reply); -} - -void JsonAPI::incommingLogMessage(const Logger::T_LOG_MESSAGE &msg) -{ - QJsonObject result, message; - QJsonArray messageArray; - - if (!_streaming_logging_activated) - { - _streaming_logging_activated = true; - QMetaObject::invokeMethod(LoggerManager::getInstance().data(), "getLogMessageBuffer", - Qt::DirectConnection, - Q_RETURN_ARG(QJsonArray, messageArray), - Q_ARG(Logger::LogLevel, _log->getLogLevel())); - } - else - { - message["loggerName"] = msg.loggerName; - message["loggerSubName"] = msg.loggerSubName; - message["function"] = msg.function; - message["line"] = QString::number(msg.line); - message["fileName"] = msg.fileName; - message["message"] = msg.message; - message["levelString"] = msg.levelString; - message["utime"] = QString::number(msg.utime); - - messageArray.append(message); - } - - result.insert("messages", messageArray); - _streaming_logging_reply["result"] = result; - - // send the result - emit callbackMessage(_streaming_logging_reply); -} - -void JsonAPI::newPendingTokenRequest(const QString &id, const QString &comment) -{ - QJsonObject obj; - obj["comment"] = comment; - obj["id"] = id; - obj["timeout"] = 180000; - - sendSuccessDataReply(QJsonDocument(obj), "authorize-tokenRequest", 1); -} - -void JsonAPI::handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &id, const int &tan) -{ - const QString cmd = "authorize-requestToken"; - QJsonObject result; - result["token"] = token; - result["comment"] = comment; - result["id"] = id; - - if (success) - sendSuccessDataReply(QJsonDocument(result), cmd, tan); - else - sendErrorReply("Token request timeout or denied", cmd, tan); -} void JsonAPI::handleInstanceStateChange(InstanceState state, quint8 instance, const QString& /*name */) { @@ -2068,24 +1422,45 @@ void JsonAPI::handleInstanceStateChange(InstanceState state, quint8 instance, co { handleInstanceSwitch(); } - break; + break; case InstanceState::H_STARTED: case InstanceState::H_STOPPED: case InstanceState::H_CREATED: case InstanceState::H_DELETED: - default: - break; + break; } } void JsonAPI::stopDataConnections() { - LoggerManager::getInstance()->disconnect(); - _streaming_logging_activated = false; _jsonCB->resetSubscriptions(); - // led stream colors - disconnect(_hyperion, &Hyperion::rawLedColors, this, 0); - _ledStreamTimer->stop(); - disconnect(_ledStreamConnection); + LoggerManager::getInstance()->disconnect(); +} + +QString JsonAPI::findCommand (const QString& jsonString) +{ + QString commandValue {"unknown"}; + + // Define a regular expression pattern to match the value associated with the key "command" + static QRegularExpression regex("\"command\"\\s*:\\s*\"([^\"]+)\""); + QRegularExpressionMatch match = regex.match(jsonString); + + if (match.hasMatch()) { + commandValue = match.captured(1); + } + return commandValue; +} + +int JsonAPI::findTan (const QString& jsonString) +{ + int tanValue {0}; + static QRegularExpression regex("\"tan\"\\s*:\\s*(\\d+)"); + QRegularExpressionMatch match = regex.match(jsonString); + + if (match.hasMatch()) { + QString valueStr = match.captured(1); + tanValue = valueStr.toInt(); + } + return tanValue; } diff --git a/libsrc/api/JsonCB.cpp b/libsrc/api/JsonCB.cpp deleted file mode 100644 index e3c4b32a..00000000 --- a/libsrc/api/JsonCB.cpp +++ /dev/null @@ -1,416 +0,0 @@ -// proj incl -#include - -// hyperion -#include - -// HyperionIManager -#include -// components - -#include -// priorityMuxer - -#include - -// utils -#include - -// qt -#include -#include - -// Image to led map helper -#include - -using namespace hyperion; - -JsonCB::JsonCB(QObject* parent) - : QObject(parent) - , _hyperion(nullptr) - , _componentRegister(nullptr) - , _prioMuxer(nullptr) -{ - _availableCommands << "components-update" << "priorities-update" << "imageToLedMapping-update" - << "adjustment-update" << "videomode-update" << "settings-update" << "leds-update" << "instance-update" << "token-update"; - - #if defined(ENABLE_EFFECTENGINE) - _availableCommands << "effects-update"; - #endif - - qRegisterMetaType("InputsMap"); -} - -bool JsonCB::subscribeFor(const QString& type, bool unsubscribe) -{ - if(!_availableCommands.contains(type)) - return false; - - if(unsubscribe) - _subscribedCommands.removeAll(type); - else - _subscribedCommands << type; - - if(type == "components-update") - { - if(unsubscribe) - disconnect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState); - else - connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState, Qt::UniqueConnection); - } - - if(type == "priorities-update") - { - if (unsubscribe) - disconnect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate); - else - connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection); - } - - if(type == "imageToLedMapping-update") - { - if(unsubscribe) - disconnect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCB::handleImageToLedsMappingChange); - else - connect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCB::handleImageToLedsMappingChange, Qt::UniqueConnection); - } - - if(type == "adjustment-update") - { - if(unsubscribe) - disconnect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCB::handleAdjustmentChange); - else - connect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCB::handleAdjustmentChange, Qt::UniqueConnection); - } - - if(type == "videomode-update") - { - if(unsubscribe) - disconnect(_hyperion, &Hyperion::newVideoMode, this, &JsonCB::handleVideoModeChange); - else - connect(_hyperion, &Hyperion::newVideoMode, this, &JsonCB::handleVideoModeChange, Qt::UniqueConnection); - } - -#if defined(ENABLE_EFFECTENGINE) - if(type == "effects-update") - { - if(unsubscribe) - disconnect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCB::handleEffectListChange); - else - connect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCB::handleEffectListChange, Qt::UniqueConnection); - } -#endif - - if(type == "settings-update") - { - if(unsubscribe) - disconnect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange); - else - connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange, Qt::UniqueConnection); - } - - if(type == "leds-update") - { - if(unsubscribe) - disconnect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange); - else - connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange, Qt::UniqueConnection); - } - - - if(type == "instance-update") - { - if(unsubscribe) - disconnect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange); - else - connect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange, Qt::UniqueConnection); - } - - if (type == "token-update") - { - if (unsubscribe) - disconnect(AuthManager::getInstance(), &AuthManager::tokenChange, this, &JsonCB::handleTokenChange); - else - connect(AuthManager::getInstance(), &AuthManager::tokenChange, this, &JsonCB::handleTokenChange, Qt::UniqueConnection); - } - - return true; -} - -void JsonCB::resetSubscriptions() -{ - for(const auto & entry : getSubscribedCommands()) - { - subscribeFor(entry, true); - } -} - -void JsonCB::setSubscriptionsTo(Hyperion* hyperion) -{ - assert(hyperion); - - // get current subs - QStringList currSubs(getSubscribedCommands()); - - // stop subs - resetSubscriptions(); - - // update pointer - _hyperion = hyperion; - _componentRegister = _hyperion->getComponentRegister(); - _prioMuxer = _hyperion->getMuxerInstance(); - - // re-apply subs - for(const auto & entry : currSubs) - { - subscribeFor(entry); - } -} - -void JsonCB::doCallback(const QString& cmd, const QVariant& data) -{ - QJsonObject obj; - obj["instance"] = _hyperion->getInstanceIndex(); - obj["command"] = cmd; - - if (data.userType() == QMetaType::QJsonArray) - obj["data"] = data.toJsonArray(); - else - obj["data"] = data.toJsonObject(); - - emit newCallback(obj); -} - -void JsonCB::handleComponentState(hyperion::Components comp, bool state) -{ - QJsonObject data; - data["name"] = componentToIdString(comp); - data["enabled"] = state; - - doCallback("components-update", QVariant(data)); -} - -void JsonCB::handlePriorityUpdate(int currentPriority, const PriorityMuxer::InputsMap& activeInputs) -{ - QJsonObject data; - QJsonArray priorities; - uint64_t now = QDateTime::currentMSecsSinceEpoch(); - QList activePriorities = activeInputs.keys(); - - activePriorities.removeAll(PriorityMuxer::LOWEST_PRIORITY); - - for (int priority : std::as_const(activePriorities)) { - - const Hyperion::InputInfo& priorityInfo = activeInputs[priority]; - - QJsonObject item; - item["priority"] = priority; - - if (priorityInfo.timeoutTime_ms > 0 ) - { - item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now); - } - - // owner has optional informations to the component - if(!priorityInfo.owner.isEmpty()) - { - item["owner"] = priorityInfo.owner; - } - - item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); - item["origin"] = priorityInfo.origin; - item["active"] = (priorityInfo.timeoutTime_ms >= -1); - item["visible"] = (priority == currentPriority); - - if(priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) - { - QJsonObject LEDcolor; - - // add RGB Value to Array - QJsonArray RGBValue; - RGBValue.append(priorityInfo.ledColors.begin()->red); - RGBValue.append(priorityInfo.ledColors.begin()->green); - RGBValue.append(priorityInfo.ledColors.begin()->blue); - LEDcolor.insert("RGB", RGBValue); - - uint16_t Hue; - float Saturation; - float Luminace; - - // add HSL Value to Array - QJsonArray HSLValue; - ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, - priorityInfo.ledColors.begin()->green, - priorityInfo.ledColors.begin()->blue, - Hue, Saturation, Luminace); - - HSLValue.append(Hue); - HSLValue.append(Saturation); - HSLValue.append(Luminace); - LEDcolor.insert("HSL", HSLValue); - - item["value"] = LEDcolor; - } - priorities.append(item); - } - - data["priorities"] = priorities; - data["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled(); - - doCallback("priorities-update", QVariant(data)); -} - -void JsonCB::handleImageToLedsMappingChange(int mappingType) -{ - QJsonObject data; - data["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(mappingType); - - doCallback("imageToLedMapping-update", QVariant(data)); -} - -void JsonCB::handleAdjustmentChange() -{ - QJsonArray adjustmentArray; - for (const QString& adjustmentId : _hyperion->getAdjustmentIds()) - { - const ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId); - if (colorAdjustment == nullptr) - { - continue; - } - - QJsonObject adjustment; - adjustment["id"] = adjustmentId; - - QJsonArray whiteAdjust; - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR()); - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG()); - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB()); - adjustment.insert("white", whiteAdjust); - - QJsonArray redAdjust; - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR()); - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG()); - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB()); - adjustment.insert("red", redAdjust); - - QJsonArray greenAdjust; - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR()); - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG()); - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB()); - adjustment.insert("green", greenAdjust); - - QJsonArray blueAdjust; - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR()); - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG()); - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB()); - adjustment.insert("blue", blueAdjust); - - QJsonArray cyanAdjust; - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR()); - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG()); - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB()); - adjustment.insert("cyan", cyanAdjust); - - QJsonArray magentaAdjust; - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR()); - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG()); - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB()); - adjustment.insert("magenta", magentaAdjust); - - QJsonArray yellowAdjust; - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR()); - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG()); - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB()); - adjustment.insert("yellow", yellowAdjust); - - adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold(); - adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored(); - adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness(); - adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation(); - adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR(); - adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG(); - adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB(); - - adjustmentArray.append(adjustment); - } - - doCallback("adjustment-update", QVariant(adjustmentArray)); -} - -void JsonCB::handleVideoModeChange(VideoMode mode) -{ - QJsonObject data; - data["videomode"] = QString(videoMode2String(mode)); - doCallback("videomode-update", QVariant(data)); -} - -#if defined(ENABLE_EFFECTENGINE) -void JsonCB::handleEffectListChange() -{ - QJsonArray effectList; - QJsonObject effects; - const std::list & effectsDefinitions = _hyperion->getEffects(); - for (const EffectDefinition & effectDefinition : effectsDefinitions) - { - QJsonObject effect; - effect["name"] = effectDefinition.name; - effect["file"] = effectDefinition.file; - effect["script"] = effectDefinition.script; - effect["args"] = effectDefinition.args; - effectList.append(effect); - }; - effects["effects"] = effectList; - doCallback("effects-update", QVariant(effects)); -} -#endif - -void JsonCB::handleSettingsChange(settings::type type, const QJsonDocument& data) -{ - QJsonObject dat; - if(data.isObject()) - dat[typeToString(type)] = data.object(); - else - dat[typeToString(type)] = data.array(); - - doCallback("settings-update", QVariant(dat)); -} - -void JsonCB::handleLedsConfigChange(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("running", entry["running"].toBool()); - arr.append(obj); - } - doCallback("instance-update", QVariant(arr)); -} - -void JsonCB::handleTokenChange(const QVector &def) -{ - QJsonArray arr; - for (const auto &entry : def) - { - QJsonObject sub; - sub["comment"] = entry.comment; - sub["id"] = entry.id; - sub["last_use"] = entry.lastUse; - arr.push_back(sub); - } - doCallback("token-update", QVariant(arr)); -} diff --git a/libsrc/api/JsonCallbacks.cpp b/libsrc/api/JsonCallbacks.cpp new file mode 100644 index 00000000..8f89b449 --- /dev/null +++ b/libsrc/api/JsonCallbacks.cpp @@ -0,0 +1,451 @@ +// proj incl +#include +#include +#include + +// hyperion +#include + +// HyperionIManager +#include +// components + +#include +// priorityMuxer + +#include + +// utils +#include + +// qt +#include +#include +#include +#include + +// Image to led map helper + +#include + +using namespace hyperion; + +JsonCallbacks::JsonCallbacks(Logger *log, const QString& peerAddress, QObject* parent) + : QObject(parent) + , _log (log) + , _hyperion(nullptr) + , _peerAddress (peerAddress) + , _componentRegister(nullptr) + , _prioMuxer(nullptr) + , _islogMsgStreamingActive(false) +{ + qRegisterMetaType("InputsMap"); +} + +bool JsonCallbacks::subscribe(const Subscription::Type cmd) +{ + switch (cmd) { + case Subscription::AdjustmentUpdate: + connect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCallbacks::handleAdjustmentChange); + break; + case Subscription::ComponentsUpdate: + connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCallbacks::handleComponentState); + break; +#if defined(ENABLE_EFFECTENGINE) + case Subscription::EffectsUpdate: + connect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCallbacks::handleEffectListChange); + break; +#endif + case Subscription::ImageToLedMappingUpdate: + connect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCallbacks::handleImageToLedsMappingChange); + break; + case Subscription::ImageUpdate: + connect(_hyperion, &Hyperion::currentImage, this, &JsonCallbacks::handleImageUpdate); + break; + case Subscription::InstanceUpdate: + connect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCallbacks::handleInstanceChange); + break; + case Subscription::LedColorsUpdate: + connect(_hyperion, &Hyperion::rawLedColors, this, &JsonCallbacks::handleLedColorUpdate); + break; + case Subscription::LedsUpdate: + connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCallbacks::handleLedsConfigChange); + break; + case Subscription::LogMsgUpdate: + if (!_islogMsgStreamingActive) + { + handleLogMessageUpdate (Logger::T_LOG_MESSAGE{}); // needed to trigger log sending + _islogMsgStreamingActive = true; + Debug(_log, "log streaming activated for client %s", _peerAddress.toStdString().c_str()); + } + connect(LoggerManager::getInstance().data(), &LoggerManager::newLogMessage, this, &JsonCallbacks::handleLogMessageUpdate); + break; + case Subscription::PrioritiesUpdate: + connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCallbacks::handlePriorityUpdate); + break; + case Subscription::SettingsUpdate: + connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCallbacks::handleSettingsChange); + break; + case Subscription::TokenUpdate: + connect(AuthManager::getInstance(), &AuthManager::tokenChange, this, &JsonCallbacks::handleTokenChange, Qt::AutoConnection); + break; + case Subscription::VideomodeUpdate: + connect(_hyperion, &Hyperion::newVideoMode, this, &JsonCallbacks::handleVideoModeChange); + break; + + default: + return false; + } + + _subscribedCommands.insert(cmd); + + return true; +} + +bool JsonCallbacks::subscribe(const QString& cmd) +{ + JsonApiSubscription subscription = ApiSubscriptionRegister::getSubscriptionInfo(cmd); + if (subscription.cmd == Subscription::Unknown) + { + return false; + } + return subscribe(subscription.cmd); +} + +QStringList JsonCallbacks::subscribe(const QJsonArray& subscriptions) +{ + QJsonArray subsArr; + if (subscriptions.contains("all")) + { + for (const auto& entry : getCommands(false)) + { + subsArr.append(entry); + } + } + else + { + subsArr = subscriptions; + } + + QStringList invalidSubscriptions; + for (auto it = subsArr.begin(); it != subsArr.end(); ++it) + { + const QJsonValue& entry = *it; + if (!subscribe(entry.toString())) + { + invalidSubscriptions.append(entry.toString()); + } + } + return invalidSubscriptions; +} + +bool JsonCallbacks::unsubscribe(const Subscription::Type cmd) +{ + _subscribedCommands.remove(cmd); + + switch (cmd) { + case Subscription::AdjustmentUpdate: + disconnect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCallbacks::handleAdjustmentChange); + break; + case Subscription::ComponentsUpdate: + disconnect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCallbacks::handleComponentState); + break; +#if defined(ENABLE_EFFECTENGINE) + case Subscription::EffectsUpdate: + disconnect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCallbacks::handleEffectListChange); + break; +#endif + case Subscription::ImageToLedMappingUpdate: + disconnect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCallbacks::handleImageToLedsMappingChange); + break; + case Subscription::ImageUpdate: + disconnect(_hyperion, &Hyperion::currentImage, this, &JsonCallbacks::handleImageUpdate); + break; + case Subscription::InstanceUpdate: + disconnect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCallbacks::handleInstanceChange); + break; + case Subscription::LedColorsUpdate: + disconnect(_hyperion, &Hyperion::rawLedColors, this, &JsonCallbacks::handleLedColorUpdate); + break; + case Subscription::LedsUpdate: + disconnect(_hyperion, &Hyperion::settingsChanged, this, &JsonCallbacks::handleLedsConfigChange); + break; + case Subscription::LogMsgUpdate: + disconnect(LoggerManager::getInstance().data(), &LoggerManager::newLogMessage, this, &JsonCallbacks::handleLogMessageUpdate); + if (_islogMsgStreamingActive) + { + _islogMsgStreamingActive = false; + Debug(_log, "log streaming deactivated for client %s", _peerAddress.toStdString().c_str()); + } + break; + case Subscription::PrioritiesUpdate: + disconnect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCallbacks::handlePriorityUpdate); + break; + case Subscription::SettingsUpdate: + disconnect(_hyperion, &Hyperion::settingsChanged, this, &JsonCallbacks::handleSettingsChange); + break; + case Subscription::TokenUpdate: + disconnect(AuthManager::getInstance(), &AuthManager::tokenChange, this, &JsonCallbacks::handleTokenChange); + break; + case Subscription::VideomodeUpdate: + disconnect(_hyperion, &Hyperion::newVideoMode, this, &JsonCallbacks::handleVideoModeChange); + break; + + default: + return false; + } + return true; +} + +bool JsonCallbacks::unsubscribe(const QString& cmd) +{ + JsonApiSubscription subscription = ApiSubscriptionRegister::getSubscriptionInfo(cmd); + if (subscription.cmd == Subscription::Unknown) + { + return false; + } + return unsubscribe(subscription.cmd); +} + +QStringList JsonCallbacks::unsubscribe(const QJsonArray& subscriptions) +{ + QJsonArray subsArr; + if (subscriptions.contains("all")) + { + for (const auto& entry : getCommands(false)) + { + subsArr.append(entry); + } + } + else + { + subsArr = subscriptions; + } + + QStringList invalidSubscriptions; + for (auto it = subsArr.begin(); it != subsArr.end(); ++it) + { + const QJsonValue& entry = *it; + if (!unsubscribe(entry.toString())) + { + invalidSubscriptions.append(entry.toString()); + } + } + return invalidSubscriptions; +} + +void JsonCallbacks::resetSubscriptions() +{ + for (QSet::const_iterator it = _subscribedCommands.constBegin(); it != _subscribedCommands.constEnd(); ++it) + { + unsubscribe(*it); + } +} + +void JsonCallbacks::setSubscriptionsTo(Hyperion* hyperion) +{ + assert(hyperion); + + // get current subs + const QSet currSubs(_subscribedCommands); + + // stop subs + resetSubscriptions(); + + // update pointer + _hyperion = hyperion; + _componentRegister = _hyperion->getComponentRegister(); + _prioMuxer = _hyperion->getMuxerInstance(); + + // re-apply subs + for(const auto & entry : currSubs) + { + subscribe(entry); + } +} + +QStringList JsonCallbacks::getCommands(bool fullList) const +{ + QStringList commands; + for (JsonApiSubscription subscription : ApiSubscriptionRegister::getSubscriptionLookup()) + { + if (fullList || subscription.isAll) + { + commands << Subscription::toString(subscription.cmd); + } + } + return commands; +} + +QStringList JsonCallbacks::getSubscribedCommands() const +{ + QStringList commands; + for (Subscription::Type cmd : _subscribedCommands) + { + commands << Subscription::toString(cmd); + } + return commands; +} + +void JsonCallbacks::doCallback(Subscription::Type cmd, const QVariant& data) +{ + QJsonObject obj; + obj["instance"] = _hyperion->getInstanceIndex(); + obj["command"] = Subscription::toString(cmd); + + if (data.userType() == QMetaType::QJsonArray) { + obj["data"] = data.toJsonArray(); + } else { + obj["data"] = data.toJsonObject(); + } + + emit newCallback(obj); +} + +void JsonCallbacks::handleComponentState(hyperion::Components comp, bool state) +{ + QJsonObject data; + data["name"] = componentToIdString(comp); + data["enabled"] = state; + + doCallback(Subscription::ComponentsUpdate, QVariant(data)); +} + +void JsonCallbacks::handlePriorityUpdate(int currentPriority, const PriorityMuxer::InputsMap& activeInputs) +{ + QJsonObject data; + data["priorities"] = JsonInfo::getPrioritiestInfo(currentPriority, activeInputs); + data["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled(); + + doCallback(Subscription::PrioritiesUpdate, QVariant(data)); +} + +void JsonCallbacks::handleImageToLedsMappingChange(int mappingType) +{ + QJsonObject data; + data["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(mappingType); + + doCallback(Subscription::ImageToLedMappingUpdate, QVariant(data)); +} + +void JsonCallbacks::handleAdjustmentChange() +{ + doCallback(Subscription::AdjustmentUpdate, JsonInfo::getAdjustmentInfo(_hyperion,_log)); +} + +void JsonCallbacks::handleVideoModeChange(VideoMode mode) +{ + QJsonObject data; + data["videomode"] = QString(videoMode2String(mode)); + doCallback(Subscription::VideomodeUpdate, QVariant(data)); +} + +#if defined(ENABLE_EFFECTENGINE) +void JsonCallbacks::handleEffectListChange() +{ + QJsonObject effects; + effects["effects"] = JsonInfo::getEffects(_hyperion); + doCallback(Subscription::EffectsUpdate, QVariant(effects)); +} +#endif + +void JsonCallbacks::handleSettingsChange(settings::type type, const QJsonDocument& data) +{ + QJsonObject dat; + if(data.isObject()) { + dat[typeToString(type)] = data.object(); + } else { + dat[typeToString(type)] = data.array(); + } + + doCallback(Subscription::SettingsUpdate, QVariant(dat)); +} + +void JsonCallbacks::handleLedsConfigChange(settings::type type, const QJsonDocument& data) +{ + if(type == settings::LEDS) + { + QJsonObject dat; + dat[typeToString(type)] = data.array(); + doCallback(Subscription::LedsUpdate, QVariant(dat)); + } +} + +void JsonCallbacks::handleInstanceChange() +{ + doCallback(Subscription::InstanceUpdate, JsonInfo::getInstanceInfo()); +} + +void JsonCallbacks::handleTokenChange(const QVector &def) +{ + QJsonArray arr; + for (const auto &entry : def) + { + QJsonObject sub; + sub["comment"] = entry.comment; + sub["id"] = entry.id; + sub["last_use"] = entry.lastUse; + arr.push_back(sub); + } + doCallback(Subscription::TokenUpdate, QVariant(arr)); +} + +void JsonCallbacks::handleLedColorUpdate(const std::vector &ledColors) +{ + QJsonObject result; + QJsonArray leds; + + for (const auto &color : ledColors) + { + leds << QJsonValue(color.red) << QJsonValue(color.green) << QJsonValue(color.blue); + } + result["leds"] = leds; + + doCallback(Subscription::LedColorsUpdate, QVariant(result)); +} + +void JsonCallbacks::handleImageUpdate(const Image &image) +{ + QImage jpgImage(reinterpret_cast(image.memptr()), image.width(), image.height(), qsizetype(3) * image.width(), QImage::Format_RGB888); + QByteArray byteArray; + QBuffer buffer(&byteArray); + buffer.open(QIODevice::WriteOnly); + jpgImage.save(&buffer, "jpg"); + + QJsonObject result; + result["image"] = "data:image/jpg;base64," + QString(byteArray.toBase64()); + + doCallback(Subscription::ImageUpdate, QVariant(result)); +} + +void JsonCallbacks::handleLogMessageUpdate(const Logger::T_LOG_MESSAGE &msg) +{ + QJsonObject result; + QJsonObject message; + QJsonArray messageArray; + + if (!_islogMsgStreamingActive) + { + _islogMsgStreamingActive = true; + QMetaObject::invokeMethod(LoggerManager::getInstance().data(), "getLogMessageBuffer", + Qt::DirectConnection, + Q_RETURN_ARG(QJsonArray, messageArray), + Q_ARG(Logger::LogLevel, _log->getLogLevel())); + } + else + { + message["loggerName"] = msg.loggerName; + message["loggerSubName"] = msg.loggerSubName; + message["function"] = msg.function; + message["line"] = QString::number(msg.line); + message["fileName"] = msg.fileName; + message["message"] = msg.message; + message["levelString"] = msg.levelString; + message["utime"] = QString::number(msg.utime); + + messageArray.append(message); + } + result.insert("messages", messageArray); + + doCallback(Subscription::LogMsgUpdate, QVariant(result)); +} diff --git a/libsrc/api/JsonInfo.cpp b/libsrc/api/JsonInfo.cpp new file mode 100644 index 00000000..e2a73ffe --- /dev/null +++ b/libsrc/api/JsonInfo.cpp @@ -0,0 +1,620 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include // Required to determine the cmake options + +#include +#include + + +QJsonArray JsonInfo::getAdjustmentInfo(const Hyperion* hyperion, Logger* log) +{ + QJsonArray adjustmentArray; + for (const QString &adjustmentId : hyperion->getAdjustmentIds()) + { + const ColorAdjustment *colorAdjustment = hyperion->getAdjustment(adjustmentId); + if (colorAdjustment == nullptr) + { + Error(log, "Incorrect color adjustment id: %s", QSTRING_CSTR(adjustmentId)); + continue; + } + + QJsonObject adjustment; + adjustment["id"] = adjustmentId; + + QJsonArray whiteAdjust; + whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR()); + whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG()); + whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB()); + adjustment.insert("white", whiteAdjust); + + QJsonArray redAdjust; + redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR()); + redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG()); + redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB()); + adjustment.insert("red", redAdjust); + + QJsonArray greenAdjust; + greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR()); + greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG()); + greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB()); + adjustment.insert("green", greenAdjust); + + QJsonArray blueAdjust; + blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR()); + blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG()); + blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB()); + adjustment.insert("blue", blueAdjust); + + QJsonArray cyanAdjust; + cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR()); + cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG()); + cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB()); + adjustment.insert("cyan", cyanAdjust); + + QJsonArray magentaAdjust; + magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR()); + magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG()); + magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB()); + adjustment.insert("magenta", magentaAdjust); + + QJsonArray yellowAdjust; + yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR()); + yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG()); + yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB()); + adjustment.insert("yellow", yellowAdjust); + + adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold(); + adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored(); + adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness(); + adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation(); + adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR(); + adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG(); + adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB(); + + adjustment["saturationGain"] = colorAdjustment->_okhsvTransform.getSaturationGain(); + adjustment["brightnessGain"] = colorAdjustment->_okhsvTransform.getBrightnessGain(); + + adjustmentArray.append(adjustment); + } + return adjustmentArray; +} + +QJsonArray JsonInfo::getPrioritiestInfo(const Hyperion* hyperion) +{ + return getPrioritiestInfo(hyperion->getCurrentPriority(), hyperion->getPriorityInfo()); +} + +QJsonArray JsonInfo::getPrioritiestInfo(int currentPriority, const PriorityMuxer::InputsMap& activeInputs) +{ + QJsonArray priorities; + int64_t now = QDateTime::currentMSecsSinceEpoch(); + + QList activePriorities = activeInputs.keys(); + activePriorities.removeAll(PriorityMuxer::LOWEST_PRIORITY); + + for(int priority : std::as_const(activePriorities)) + { + const PriorityMuxer::InputInfo priorityInfo = activeInputs.value(priority); + + QJsonObject item; + item["priority"] = priority; + + if (priorityInfo.timeoutTime_ms > 0 ) + { + item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now); + } + + // owner has optional informations to the component + if (!priorityInfo.owner.isEmpty()) + { + item["owner"] = priorityInfo.owner; + } + + item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); + item["origin"] = priorityInfo.origin; + item["active"] = (priorityInfo.timeoutTime_ms >= -1); + item["visible"] = (priority == currentPriority); + + if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) + { + QJsonObject LEDcolor; + + // add RGB Value to Array + QJsonArray RGBValue; + RGBValue.append(priorityInfo.ledColors.begin()->red); + RGBValue.append(priorityInfo.ledColors.begin()->green); + RGBValue.append(priorityInfo.ledColors.begin()->blue); + LEDcolor.insert("RGB", RGBValue); + + uint16_t Hue; + float Saturation; + float Luminace; + + // add HSL Value to Array + QJsonArray HSLValue; + ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, + priorityInfo.ledColors.begin()->green, + priorityInfo.ledColors.begin()->blue, + Hue, Saturation, Luminace); + + HSLValue.append(static_cast(Hue)); + HSLValue.append(static_cast(Saturation)); + HSLValue.append(static_cast(Luminace)); + LEDcolor.insert("HSL", HSLValue); + + item["value"] = LEDcolor; + } + + (priority == currentPriority) + ? priorities.prepend(item) + : priorities.append(item); + } + return priorities; +} + +QJsonArray JsonInfo::getEffects(const Hyperion* hyperion) +{ + QJsonArray effects; +#if defined(ENABLE_EFFECTENGINE) + // collect effect info + + const std::list &effectsDefinitions = hyperion->getEffects(); + for (const EffectDefinition &effectDefinition : effectsDefinitions) + { + QJsonObject effect; + effect["name"] = effectDefinition.name; + effect["file"] = effectDefinition.file; + effect["script"] = effectDefinition.script; + effect["args"] = effectDefinition.args; + effects.append(effect); + } +#endif + return effects; +} + +QJsonObject JsonInfo::getAvailableLedDevices() +{ + // get available led devices + QJsonObject ledDevices; + QJsonArray availableLedDevices; + for (const auto& dev : LedDeviceWrapper::getDeviceMap()) + { + availableLedDevices.append(dev.first); + } + + ledDevices["available"] = availableLedDevices; + + return ledDevices; +} + +QJsonArray JsonInfo::getAvailableScreenGrabbers() +{ + QJsonArray availableScreenGrabbers; + for (const auto& grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::SCREEN)) + { + availableScreenGrabbers.append(grabber); + } + return availableScreenGrabbers; +} + +QJsonArray JsonInfo::getAvailableVideoGrabbers() +{ + QJsonArray availableVideoGrabbers; + for (const auto& grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::VIDEO)) + { + availableVideoGrabbers.append(grabber); + } + return availableVideoGrabbers; +} +QJsonArray JsonInfo::getAvailableAudioGrabbers() +{ + QJsonArray availableAudioGrabbers; + for (const auto& grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::AUDIO)) + { + availableAudioGrabbers.append(grabber); + } + return availableAudioGrabbers; +} + +QJsonObject JsonInfo::getGrabbers(const Hyperion* hyperion) +{ + QJsonObject grabbers; + // SCREEN + QJsonObject screenGrabbers; + if (GrabberWrapper::getInstance() != nullptr) + { + const QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(hyperion->getInstanceIndex(), GrabberTypeFilter::SCREEN); + QJsonArray activeGrabberNames; + for (const auto& grabberName : activeGrabbers) + { + activeGrabberNames.append(grabberName); + } + + screenGrabbers["active"] = activeGrabberNames; + } + screenGrabbers["available"] = getAvailableScreenGrabbers(); + + // VIDEO + QJsonObject videoGrabbers; + if (GrabberWrapper::getInstance() != nullptr) + { + const QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(hyperion->getInstanceIndex(), GrabberTypeFilter::VIDEO); + QJsonArray activeGrabberNames; + for (const auto& grabberName : activeGrabbers) + { + activeGrabberNames.append(grabberName); + } + + videoGrabbers["active"] = activeGrabberNames; + } + videoGrabbers["available"] = getAvailableVideoGrabbers(); + + // AUDIO + QJsonObject audioGrabbers; + if (GrabberWrapper::getInstance() != nullptr) + { + const QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(hyperion->getInstanceIndex(), GrabberTypeFilter::AUDIO); + + QJsonArray activeGrabberNames; + for (const auto& grabberName : activeGrabbers) + { + activeGrabberNames.append(grabberName); + } + + audioGrabbers["active"] = activeGrabberNames; + } + audioGrabbers["available"] = getAvailableAudioGrabbers() ; + + grabbers.insert("screen", screenGrabbers); + grabbers.insert("video", videoGrabbers); + grabbers.insert("audio", audioGrabbers); + + return grabbers; +} + +QJsonObject JsonInfo::getCecInfo() +{ + QJsonObject cecInfo; +#if defined(ENABLE_CEC) + cecInfo["enabled"] = true; +#else + cecInfo["enabled"] = false; +#endif + return cecInfo; +} + +QJsonArray JsonInfo::getServices() +{ + // get available services + QJsonArray services; + +#if defined(ENABLE_BOBLIGHT_SERVER) + services.append("boblight"); +#endif + +#if defined(ENABLE_CEC) + services.append("cec"); +#endif + +#if defined(ENABLE_EFFECTENGINE) + services.append("effectengine"); +#endif + +#if defined(ENABLE_FORWARDER) + services.append("forwarder"); +#endif + +#if defined(ENABLE_FLATBUF_SERVER) + services.append("flatbuffer"); +#endif + +#if defined(ENABLE_PROTOBUF_SERVER) + services.append("protobuffer"); +#endif + +#if defined(ENABLE_MDNS) + services.append("mDNS"); +#endif + services.append("SSDP"); + + if (!getAvailableScreenGrabbers().isEmpty() || !getAvailableVideoGrabbers().isEmpty() || services.contains("flatbuffer") || services.contains("protobuffer")) + { + services.append("borderdetection"); + } + return services; +} + +QJsonArray JsonInfo::getComponents(const Hyperion* hyperion) +{ + // get available components + QJsonArray component; + std::map components = hyperion->getComponentRegister()->getRegister(); + for (auto comp : components) + { + QJsonObject item; + item["name"] = QString::fromStdString(hyperion::componentToIdString(comp.first)); + item["enabled"] = comp.second; + + component.append(item); + } + return component; +} + +QJsonArray JsonInfo::getInstanceInfo() +{ + QJsonArray instanceInfo; + 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("running", entry["running"].toBool()); + instanceInfo.append(obj); + } + return instanceInfo; +} + +QJsonArray JsonInfo::getTransformationInfo(const Hyperion* hyperion) +{ + // TRANSFORM INFORMATION (DEFAULT VALUES) + QJsonArray transformArray; + for (const QString &transformId : hyperion->getAdjustmentIds()) + { + QJsonObject transform; + QJsonArray blacklevel; + QJsonArray whitelevel; + QJsonArray gamma; + QJsonArray threshold; + + transform["id"] = transformId; + transform["saturationGain"] = 1.0; + transform["brightnessGain"] = 1.0; + transform["saturationLGain"] = 1.0; + transform["luminanceGain"] = 1.0; + transform["luminanceMinimum"] = 0.0; + + for (int i = 0; i < 3; i++) + { + blacklevel.append(0.0); + whitelevel.append(1.0); + gamma.append(2.50); + threshold.append(0.0); + } + + transform.insert("blacklevel", blacklevel); + transform.insert("whitelevel", whitelevel); + transform.insert("gamma", gamma); + transform.insert("threshold", threshold); + + transformArray.append(transform); + } + return transformArray; +} + +QJsonArray JsonInfo::getActiveEffects(const Hyperion* hyperion) +{ + // ACTIVE EFFECT INFO + QJsonArray activeEffects; +#if defined(ENABLE_EFFECTENGINE) + for (const ActiveEffectDefinition &activeEffectDefinition : hyperion->getActiveEffects()) + { + if (activeEffectDefinition.priority != PriorityMuxer::LOWEST_PRIORITY - 1) + { + QJsonObject activeEffect; + activeEffect["script"] = activeEffectDefinition.script; + activeEffect["name"] = activeEffectDefinition.name; + activeEffect["priority"] = activeEffectDefinition.priority; + activeEffect["timeout"] = activeEffectDefinition.timeout; + activeEffect["args"] = activeEffectDefinition.args; + activeEffects.append(activeEffect); + } + } +#endif + return activeEffects; +} + +QJsonArray JsonInfo::getActiveColors(const Hyperion* hyperion) +{ + // ACTIVE STATIC LED COLOR + QJsonArray activeLedColors; + const Hyperion::InputInfo &priorityInfo = hyperion->getPriorityInfo(hyperion->getCurrentPriority()); + if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) + { + // check if LED Color not Black (0,0,0) + if ((priorityInfo.ledColors.begin()->red + + priorityInfo.ledColors.begin()->green + + priorityInfo.ledColors.begin()->blue != + 0)) + { + QJsonObject LEDcolor; + + // add RGB Value to Array + QJsonArray RGBValue; + RGBValue.append(priorityInfo.ledColors.begin()->red); + RGBValue.append(priorityInfo.ledColors.begin()->green); + RGBValue.append(priorityInfo.ledColors.begin()->blue); + LEDcolor.insert("RGB Value", RGBValue); + + uint16_t Hue; + float Saturation; + float Luminace; + + // add HSL Value to Array + QJsonArray HSLValue; + ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, + priorityInfo.ledColors.begin()->green, + priorityInfo.ledColors.begin()->blue, + Hue, Saturation, Luminace); + + HSLValue.append(static_cast(Hue)); + HSLValue.append(static_cast(Saturation)); + HSLValue.append(static_cast(Luminace)); + LEDcolor.insert("HSL Value", HSLValue); + + activeLedColors.append(LEDcolor); + } + } + return activeLedColors; +} + +QJsonObject JsonInfo::getSystemInfo(const Hyperion* hyperion) +{ + QJsonObject info; + + SysInfo::HyperionSysInfo data = SysInfo::get(); + QJsonObject systemInfo; + systemInfo["kernelType"] = data.kernelType; + systemInfo["kernelVersion"] = data.kernelVersion; + systemInfo["architecture"] = data.architecture; + systemInfo["cpuModelName"] = data.cpuModelName; + systemInfo["cpuModelType"] = data.cpuModelType; + systemInfo["cpuHardware"] = data.cpuHardware; + systemInfo["cpuRevision"] = data.cpuRevision; + systemInfo["wordSize"] = data.wordSize; + systemInfo["productType"] = data.productType; + systemInfo["productVersion"] = data.productVersion; + systemInfo["prettyName"] = data.prettyName; + systemInfo["hostName"] = data.hostName; + systemInfo["domainName"] = data.domainName; + systemInfo["isUserAdmin"] = data.isUserAdmin; + systemInfo["qtVersion"] = data.qtVersion; +#if defined(ENABLE_EFFECTENGINE) + systemInfo["pyVersion"] = data.pyVersion; +#endif + info["system"] = systemInfo; + + QJsonObject hyperionInfo; + hyperionInfo["version"] = QString(HYPERION_VERSION); + hyperionInfo["build"] = QString(HYPERION_BUILD_ID); + hyperionInfo["gitremote"] = QString(HYPERION_GIT_REMOTE); + hyperionInfo["time"] = QString(__DATE__ " " __TIME__); + hyperionInfo["id"] = AuthManager::getInstance()->getID(); + hyperionInfo["rootPath"] = HyperionIManager::getInstance()->getRootPath(); + hyperionInfo["readOnlyMode"] = hyperion->getReadOnlyMode(); + + QCoreApplication* app = QCoreApplication::instance(); + hyperionInfo["isGuiMode"] = qobject_cast(app) != nullptr; + + info["hyperion"] = hyperionInfo; + + return info; +} + +QJsonObject JsonInfo::discoverSources(const QString& sourceType, const QJsonObject& params) +{ + QJsonObject inputSourcesDiscovered; + inputSourcesDiscovered.insert("sourceType", sourceType); + + if (sourceType == "video") { + QJsonArray videoInputs = discoverVideoInputs(params); + inputSourcesDiscovered["video_sources"] = videoInputs; + } else if (sourceType == "audio") { + QJsonArray audioInputs = discoverAudioInputs(params); + inputSourcesDiscovered["audio_sources"] = audioInputs; + } else if (sourceType == "screen") { + QJsonArray screenInputs = discoverScreenInputs(params); + inputSourcesDiscovered["video_sources"] = screenInputs; + } + + return inputSourcesDiscovered; +} + +template +void JsonInfo::discoverGrabber(QJsonArray& inputs, const QJsonObject& params) const +{ + GrabberType grabber; + QJsonValue discoveryResult = grabber.discover(params); + + if (discoveryResult.isArray()) + { + inputs = discoveryResult.toArray(); + } + else + { + if (!discoveryResult.toObject().isEmpty()) + { + inputs.append(discoveryResult); + } + } +} + +QJsonArray JsonInfo::discoverVideoInputs(const QJsonObject& params) const +{ + QJsonArray videoInputs; + +#ifdef ENABLE_V4L2 + discoverGrabber(videoInputs, params); +#endif + +#ifdef ENABLE_MF + discoverGrabber(videoInputs, params); +#endif + + return videoInputs; +} + +QJsonArray JsonInfo::discoverAudioInputs(const QJsonObject& params) const +{ + QJsonArray audioInputs; + +#ifdef ENABLE_AUDIO +#ifdef WIN32 + discoverGrabber(audioInputs, params); +#endif + +#ifdef __linux__audioInputs + discoverGrabber(audioInputs, params); +#endif + +#endif + + return audioInputs; +} + +QJsonArray JsonInfo::discoverScreenInputs(const QJsonObject& params) const +{ + QJsonArray screenInputs; + +#ifdef ENABLE_QT + discoverGrabber(screenInputs, params); +#endif + +#ifdef ENABLE_DX + discoverGrabber(screenInputs, params); +#endif + +#ifdef ENABLE_X11 + discoverGrabber(screenInputs, params); +#endif + +#ifdef ENABLE_XCB + discoverGrabber(screenInputs, params); +#endif + +#if defined(ENABLE_FB) && !defined(ENABLE_AMLOGIC) + discoverGrabber(screenInputs, params); +#endif + +#ifdef ENABLE_DISPMANX + discoverGrabber(screenInputs, params); +#endif + +#ifdef ENABLE_AMLOGIC + discoverGrabber(screenInputs, params); +#endif + +#ifdef ENABLE_OSX + discoverGrabber(screenInputs, params); +#endif + + return screenInputs; +} diff --git a/libsrc/effectengine/EffectFileHandler.cpp b/libsrc/effectengine/EffectFileHandler.cpp index 3d1c77eb..734c4da2 100644 --- a/libsrc/effectengine/EffectFileHandler.cpp +++ b/libsrc/effectengine/EffectFileHandler.cpp @@ -103,7 +103,7 @@ QString EffectFileHandler::saveEffect(const QJsonObject& message) if (it != effectsSchemas.end()) { - if (!JsonUtils::validate("EffectFileHandler", message["args"].toObject(), it->schemaFile, _log)) + if (!JsonUtils::validate("EffectFileHandler", message["args"].toObject(), it->schemaFile, _log).first) { return "Error during arg validation against schema, please consult the Hyperion Log"; } @@ -298,12 +298,12 @@ bool EffectFileHandler::loadEffectDefinition(const QString& path, const QString& // Read and parse the effect json config file QJsonObject configEffect; - if (!JsonUtils::readFile(fileName, configEffect, _log)) { + if (!JsonUtils::readFile(fileName, configEffect, _log).first) { return false; } // validate effect config with effect schema(path) - if (!JsonUtils::validate(fileName, configEffect, ":effect-schema", _log)) { + if (!JsonUtils::validate(fileName, configEffect, ":effect-schema", _log).first) { return false; } @@ -335,7 +335,7 @@ bool EffectFileHandler::loadEffectSchema(const QString& path, const QString& sch { // Read and parse the effect schema file QJsonObject schemaEffect; - if (!JsonUtils::readFile(schemaFilePath, schemaEffect, _log)) + if (!JsonUtils::readFile(schemaFilePath, schemaEffect, _log).first) { return false; } diff --git a/libsrc/hyperion/AuthManager.cpp b/libsrc/hyperion/AuthManager.cpp index fd4bd071..4e60807e 100644 --- a/libsrc/hyperion/AuthManager.cpp +++ b/libsrc/hyperion/AuthManager.cpp @@ -15,7 +15,6 @@ AuthManager::AuthManager(QObject *parent, bool readonlyMode) , _authTable(new AuthTable("", this, readonlyMode)) , _metaTable(new MetaTable(this, readonlyMode)) , _pendingRequests() - , _authRequired(true) , _timer(new QTimer(this)) , _authBlockTimer(new QTimer(this)) { @@ -201,6 +200,8 @@ QVector AuthManager::getPendingRequests() const def.comment = entry.comment; def.id = entry.id; def.timeoutTime = entry.timeoutTime - QDateTime::currentMSecsSinceEpoch(); + def.tan = entry.tan; + def.caller = nullptr; finalVec.append(def); } return finalVec; @@ -208,20 +209,26 @@ QVector AuthManager::getPendingRequests() const bool AuthManager::renameToken(const QString &id, const QString &comment) { - if (_authTable->renameToken(id, comment)) + if (_authTable->idExist(id)) { - emit tokenChange(getTokenList()); - return true; + if (_authTable->renameToken(id, comment)) + { + emit tokenChange(getTokenList()); + return true; + } } return false; } bool AuthManager::deleteToken(const QString &id) { - if (_authTable->deleteToken(id)) + if (_authTable->idExist(id)) { - emit tokenChange(getTokenList()); - return true; + if (_authTable->deleteToken(id)) + { + emit tokenChange(getTokenList()); + return true; + } } return false; } @@ -231,9 +238,7 @@ void AuthManager::handleSettingsUpdate(settings::type type, const QJsonDocument if (type == settings::NETWORK) { const QJsonObject &obj = config.object(); - _authRequired = obj["apiAuth"].toBool(true); _localAuthRequired = obj["localApiAuth"].toBool(false); - _localAdminAuthRequired = obj["localAdminAuth"].toBool(true); } } diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 2a2b50d6..de719578 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -550,6 +550,11 @@ QList Hyperion::getActivePriorities() const return _muxer->getPriorities(); } +Hyperion::InputsMap Hyperion::getPriorityInfo() const +{ + return _muxer->getInputInfo(); +} + Hyperion::InputInfo Hyperion::getPriorityInfo(int priority) const { return _muxer->getInputInfo(priority); diff --git a/libsrc/hyperion/PriorityMuxer.cpp b/libsrc/hyperion/PriorityMuxer.cpp index ae4c64e8..91024744 100644 --- a/libsrc/hyperion/PriorityMuxer.cpp +++ b/libsrc/hyperion/PriorityMuxer.cpp @@ -128,6 +128,11 @@ bool PriorityMuxer::hasPriority(int priority) const return (priority == PriorityMuxer::LOWEST_PRIORITY) ? true : _activeInputs.contains(priority); } +PriorityMuxer::InputsMap PriorityMuxer::getInputInfo() const +{ + return _activeInputs; +} + PriorityMuxer::InputInfo PriorityMuxer::getInputInfo(int priority) const { auto elemIt = _activeInputs.constFind(priority); diff --git a/libsrc/hyperion/SettingsManager.cpp b/libsrc/hyperion/SettingsManager.cpp index 2c1201b1..cd5e8727 100644 --- a/libsrc/hyperion/SettingsManager.cpp +++ b/libsrc/hyperion/SettingsManager.cpp @@ -52,7 +52,7 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonly // get default config QJsonObject defaultConfig; - if (!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log)) + if (!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log).first) { throw std::runtime_error("Failed to read default config"); } diff --git a/libsrc/hyperion/schema/schema-network.json b/libsrc/hyperion/schema/schema-network.json index e159c95f..b5bca5ae 100644 --- a/libsrc/hyperion/schema/schema-network.json +++ b/libsrc/hyperion/schema/schema-network.json @@ -4,26 +4,13 @@ "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 + "propertyOrder" : 1 }, "restirctedInternetAccessAPI" : { @@ -36,7 +23,7 @@ "internetAccessAPI": true } }, - "propertyOrder" : 3 + "propertyOrder" : 2 }, "ipWhitelist" : { @@ -53,7 +40,7 @@ "restirctedInternetAccessAPI": true } }, - "propertyOrder" : 4 + "propertyOrder" : 3 }, "localApiAuth" : { @@ -66,15 +53,7 @@ "apiAuth": true } }, - "propertyOrder" : 5 - }, - "localAdminAuth" : - { - "type" : "boolean", - "title" : "edt_conf_net_localAdminAuth_title", - "required" : true, - "default" : true, - "propertyOrder" : 5 + "propertyOrder" : 4 } }, "additionalProperties" : false diff --git a/libsrc/leddevice/LedDeviceWrapper.cpp b/libsrc/leddevice/LedDeviceWrapper.cpp index cc8177fb..838a5b37 100644 --- a/libsrc/leddevice/LedDeviceWrapper.cpp +++ b/libsrc/leddevice/LedDeviceWrapper.cpp @@ -193,11 +193,17 @@ QJsonObject LedDeviceWrapper::getLedDeviceSchemas() } QJsonObject schema; - if(!JsonUtils::parse(schemaPath, data, schema, Logger::getInstance("LEDDEVICE"))) + QPair parsingResult = JsonUtils::parse(schemaPath, data, schema, Logger::getInstance("LEDDEVICE")); + if (!parsingResult.first) { - throw std::runtime_error("ERROR: JSON schema wrong of file: " + item.toStdString()); + QStringList errorList = parsingResult.second; + for (const auto& errorMessage : errorList) { + Debug(Logger::getInstance("LEDDEVICE"), "JSON parse error: %s ", QSTRING_CSTR(errorMessage)); + } + throw std::runtime_error("ERROR: JSON schema is wrong for file: " + item.toStdString()); } + schemaJson = schema; schemaJson["title"] = QString("edt_dev_spec_header_title"); diff --git a/libsrc/utils/JsonUtils.cpp b/libsrc/utils/JsonUtils.cpp index d43c5bba..3b51adf8 100644 --- a/libsrc/utils/JsonUtils.cpp +++ b/libsrc/utils/JsonUtils.cpp @@ -8,25 +8,26 @@ #include #include #include +#include namespace JsonUtils { - bool readFile(const QString& path, QJsonObject& obj, Logger* log, bool ignError) + QPair readFile(const QString& path, QJsonObject& obj, Logger* log, bool ignError) { QString data; if(!FileUtils::readFile(path, data, log, ignError)) - return false; + { + return qMakePair(false, QStringList(QString("Error reading file: %1").arg(path))); + } - if(!parse(path, data, obj, log)) - return false; - - return true; + QPair parsingResult = JsonUtils::parse(path, data, obj, log); + return parsingResult; } bool readSchema(const QString& path, QJsonObject& obj, Logger* log) { QJsonObject schema; - if(!readFile(path, schema, log)) + if(!readFile(path, schema, log).first) return false; if(!resolveRefs(schema, obj, log)) @@ -35,80 +36,91 @@ namespace JsonUtils { return true; } - bool parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log) + QPair parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log) { QJsonDocument doc; - if(!parse(path, data, doc, log)) - return false; - + QPair parsingResult = JsonUtils::parse(path, data, doc, log); obj = doc.object(); - return true; + return parsingResult; } - bool parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log) + QPair parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log) { QJsonDocument doc; - if(!parse(path, data, doc, log)) - return false; + QPair parsingResult = JsonUtils::parse(path, data, doc, log); arr = doc.array(); - return true; + return parsingResult; } - bool parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log) + QPair parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log) { - //remove Comments in data - QString cleanData = data; + QStringList errorList; + QJsonParseError error; - doc = QJsonDocument::fromJson(cleanData.toUtf8(), &error); + doc = QJsonDocument::fromJson(data.toUtf8(), &error); if (error.error != QJsonParseError::NoError) { - // report to the user the failure and their locations in the document. - int errorLine(0), errorColumn(0); + qDebug() << "error.offset: " << error.offset; - for( int i=0, count=qMin( error.offset,cleanData.size()); i validate(const QString& file, const QJsonObject& json, const QString& schemaPath, Logger* log) { // get the schema data QJsonObject schema; - if(!readFile(schemaPath, schema, log)) - return false; - if(!validate(file, json, schema, log)) - return false; - return true; + QPair readResult = readFile(schemaPath, schema, log); + if(!readResult.first) + { + return readResult; + } + QPair validationResult = validate(file, json, schema, log); + return validationResult; } - bool validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log) + QPair validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log) { + QStringList errorList; + QJsonSchemaChecker schemaChecker; schemaChecker.setSchema(schema); if (!schemaChecker.validate(json).first) { - const QStringList & errors = schemaChecker.getMessages(); - for (auto & error : errors) + const QStringList &errors = schemaChecker.getMessages(); + for (const auto& error : errors) { - Error(log, "While validating schema against json data of '%s':%s", QSTRING_CSTR(file), QSTRING_CSTR(error)); + QString errorMessage = QString("JSON parse error: %1") + .arg(error); + errorList.push_back(errorMessage); + Error(log, "%s", QSTRING_CSTR(errorMessage)); } - return false; + return qMakePair(false, errorList); } - return true; + return qMakePair(true, errorList); } bool write(const QString& filename, const QJsonObject& json, Logger* log) diff --git a/libsrc/utils/NetOrigin.cpp b/libsrc/utils/NetOrigin.cpp index d5a486e6..42693491 100644 --- a/libsrc/utils/NetOrigin.cpp +++ b/libsrc/utils/NetOrigin.cpp @@ -1,13 +1,15 @@ #include #include +#include NetOrigin* NetOrigin::instance = nullptr; NetOrigin::NetOrigin(QObject* parent, Logger* log) : QObject(parent) , _log(log) - , _internetAccessAllowed(false) + , _isInternetAccessAllowed(false) + , _isInternetAccessRestricted(false) , _ipWhitelist() { NetOrigin::instance = this; @@ -15,37 +17,73 @@ NetOrigin::NetOrigin(QObject* parent, Logger* log) bool NetOrigin::accessAllowed(const QHostAddress& address, const QHostAddress& local) const { - if(_internetAccessAllowed) - return true; + bool isAllowed {false}; - if(_ipWhitelist.contains(address)) // v4 and v6 - return true; - - if(!isLocalAddress(address, local)) + 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; + isAllowed = true; } - return true; + else + { + if(_isInternetAccessAllowed) + { + if (!_isInternetAccessRestricted) + { + isAllowed = true; + } + else + { + for (const QHostAddress &listAddress : _ipWhitelist) + { + if (address.isEqual(listAddress)) + { + isAllowed = true; + break; + } + } + WarningIf(!isAllowed, _log,"Client connection from IP address '%s' has been rejected! It's not whitelisted.",QSTRING_CSTR(address.toString())); + } + } + } + return isAllowed; } -bool NetOrigin::isLocalAddress(const QHostAddress& address, const QHostAddress& local) const + +bool NetOrigin::isLocalAddress(const QHostAddress& ipAddress, const QHostAddress& /*local*/) const { - if(address.protocol() == QAbstractSocket::IPv4Protocol) + QHostAddress address = ipAddress; + + if (address.isLoopback() || address.isLinkLocal()) { - if(!address.isInSubnet(local, 24)) // 255.255.255.xxx; IPv4 0-32 - { - return false; + return true; + } + + //Convert to IPv4 to check, if an IPv6 address is an IPv4 mapped address + QHostAddress ipv4Address(address.toIPv4Address()); + if (ipv4Address != QHostAddress::AnyIPv4) // ipv4Address is not "0.0.0.0" + { + address = ipv4Address; + } + + QList allInterfaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &networkInterface : allInterfaces) { + QList addressEntries = networkInterface.addressEntries(); + for (const QNetworkAddressEntry &localNetworkAddressEntry : addressEntries) { + QHostAddress localIP = localNetworkAddressEntry.ip(); + + if(localIP.protocol() != QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) + { + continue; + } + + bool isInSubnet = address.isInSubnet(localIP, localNetworkAddressEntry.prefixLength()); + if (isInSubnet) + { + return true; + } } } - 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; + return false; } void NetOrigin::handleSettingsUpdate(settings::type type, const QJsonDocument& config) @@ -53,16 +91,19 @@ void NetOrigin::handleSettingsUpdate(settings::type type, const QJsonDocument& c if(type == settings::NETWORK) { const QJsonObject& obj = config.object(); - _internetAccessAllowed = obj["internetAccessAPI"].toBool(false); + _isInternetAccessAllowed = obj["internetAccessAPI"].toBool(false); + _isInternetAccessRestricted = obj["restirctedInternetAccessAPI"].toBool(false); + const QJsonArray arr = obj["ipWhitelist"].toArray(); - const QJsonArray& arr = obj["ipWhitelist"].toArray(); - _ipWhitelist.clear(); + _ipWhitelist.clear(); - for(const auto& e : arr) + for(const auto& item : std::as_const(arr)) { - const QString& entry = e.toString(""); + const QString& entry = item.toString(""); if(entry.isEmpty()) + { continue; + } QHostAddress host(entry); if(host.isNull()) diff --git a/src/hyperion-remote/JsonConnection.cpp b/src/hyperion-remote/JsonConnection.cpp index 669a5bf4..618f30be 100644 --- a/src/hyperion-remote/JsonConnection.cpp +++ b/src/hyperion-remote/JsonConnection.cpp @@ -125,7 +125,7 @@ void JsonConnection::setEffect(const QString &effectName, const QString & effect if (effectArgs.size() > 0) { QJsonObject effObj; - if(!JsonUtils::parse("hyperion-remote-args", effectArgs, effObj, _log)) + if(!JsonUtils::parse("hyperion-remote-args", effectArgs, effObj, _log).first) { throw std::runtime_error("Error in effect arguments, abort"); } @@ -160,7 +160,7 @@ void JsonConnection::createEffect(const QString &effectName, const QString &effe if (effectArgs.size() > 0) { QJsonObject effObj; - if(!JsonUtils::parse("hyperion-remote-args", effectScript, effObj, _log)) + if(!JsonUtils::parse("hyperion-remote-args", effectScript, effObj, _log).first) { throw std::runtime_error("Error in effect arguments, abort"); } @@ -440,7 +440,7 @@ void JsonConnection::setConfig(const QString &jsonString) if (jsonString.size() > 0) { QJsonObject configObj; - if(!JsonUtils::parse("hyperion-remote-args", jsonString, configObj, _log)) + if(!JsonUtils::parse("hyperion-remote-args", jsonString, configObj, _log).first) { throw std::runtime_error("Error in configSet arguments, abort"); }