Refactor Settings DB and Handling (#1786)

* Refactor config API

* Corrections

* Test Qt 6.8

* Revert "Test Qt 6.8"

This reverts commit eceebec49e.

* Corrections 2

* Update Changelog

* Add configFilter element for getconfig call

* Do not create errors for DB updates when in read-only mode

* Have configuration migration and validation before Hyperion starts

* Correct Tests

* Corrections

* Add migration items

* Correct windows build

* Ensure that first instance as default one exists

* Remove dependency between AuthManager and SSDPHandler

* Correct typos

* Address CodeQL findings

* Replace CamkeSettings by Presets and provide debug scenarios
This commit is contained in:
LordGrey
2024-09-30 22:03:13 +02:00
committed by GitHub
parent aed4abc03b
commit ecceb4e7ae
88 changed files with 4407 additions and 2472 deletions

View File

@@ -391,34 +391,6 @@ QString API::saveEffect(const QJsonObject &data)
}
#endif
bool API::saveSettings(const QJsonObject &data)
{
bool isSaved {true};
if (!_adminAuthorized)
{
isSaved = false;
}
else
{
QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, isSaved), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
}
return isSaved;
}
bool API::restoreSettings(const QJsonObject &data)
{
bool isRestored {true};
if (!_adminAuthorized)
{
isRestored = false;
}
else
{
QMetaObject::invokeMethod(_hyperion, "restoreSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, isRestored), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
}
return isRestored;
}
bool API::updateHyperionPassword(const QString &password, const QString &newPassword)
{
bool isPwUpdated {true};

View File

@@ -10,18 +10,42 @@
"subcommand": {
"type" : "string",
"required" : true,
"enum" : ["getconfig","getschema","setconfig","restoreconfig","reload"]
},
"instance" : {
"type" : "integer",
"minimum": 0,
"maximum": 255
"enum" : ["getconfig","getconfig-old","getschema","setconfig","restoreconfig","reload"]
},
"tan" : {
"type" : "integer"
},
"configFilter": {
"global" : {
"types": {
"type": "array",
"required": false,
"items" : {
"type" : "string"
}
}
},
"instances" : {
"ids" : {
"type": "array",
"required": true,
"items" : {},
"minItems": 1
},
"types": {
"type": "array",
"required": false,
"items" :{
"type" : "string"
}
}
}
},
"config": {
"type" : "object"
"required": false,
"$ref": "schema-settings-full-relaxed.json",
"required": false,
"$ref": "schema-settings-ui.json"
}
},
"additionalProperties": false

View File

@@ -39,6 +39,7 @@
// auth manager
#include <hyperion/AuthManager.h>
#include <db/DBConfigManager.h>
#ifdef ENABLE_MDNS
// mDNS discover
@@ -67,6 +68,8 @@ constexpr int BEARER_TOKEN_TAG_LENGTH = sizeof(BEARER_TOKEN_TAG) - 1;
const int MIN_PASSWORD_LENGTH = 8;
const int APP_TOKEN_LENGTH = 36;
const char SETTINGS_UI_SCHEMA_FILE[] = ":/schema-settings-ui.json";
const bool verbose = false;
}
@@ -697,7 +700,11 @@ void JsonAPI::handleConfigCommand(const QJsonObject& message, const JsonApiComma
handleSchemaGetCommand(message, cmd);
break;
case SubCommand::GetConfig:
case SubCommand::GetConfig:
handleConfigGetCommand(message, cmd);
break;
case SubCommand::GetConfigOld:
sendSuccessDataReply(_hyperion->getQJsonConfig(), cmd);
break;
@@ -722,45 +729,191 @@ void JsonAPI::handleConfigCommand(const QJsonObject& message, const JsonApiComma
void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
if (message.contains("config"))
if (DBManager::isReadOnly())
{
QJsonObject config = message["config"].toObject();
if (API::isHyperionEnabled())
sendErrorReply("Database Error", {"Hyperion is running in read-only mode","Configuration updates are not possible"}, cmd);
return;
}
QJsonObject config = message["config"].toObject();
if (config.contains("global") || config.contains("instances"))
{
QStringList errorDetails;
QMap<quint8, QJsonObject> instancesNewConfigs;
const QJsonArray instances = config["instances"].toArray();
if (!instances.isEmpty())
{
if ( API::saveSettings(config) ) {
sendSuccessReply(cmd);
} else {
sendErrorReply("Save settings failed", cmd);
QList<quint8> configuredInstanceIds = _instanceManager->getInstanceIds();
for (const auto &instance : instances)
{
QJsonObject instanceObject = instance.toObject();
const QJsonValue idx = instanceObject["id"];
if (idx.isDouble())
{
quint8 instanceId = static_cast<quint8>(idx.toInt());
if (configuredInstanceIds.contains(instanceId))
{
instancesNewConfigs.insert(instanceId,instanceObject.value("settings").toObject());
}
else
{
errorDetails.append(QString("Given instance id '%1' does not exist. Configuration item will be ignored").arg(instanceId));
}
}
}
}
else
const QJsonObject globalSettings = config["global"].toObject().value("settings").toObject();
if (!globalSettings.isEmpty())
{
sendErrorReply("It is not possible saving a configuration while Hyperion is disabled", cmd);
const QJsonObject instanceZeroConfig = instancesNewConfigs.value(0);
instancesNewConfigs.insert(0, JsonUtils::mergeJsonObjects(instanceZeroConfig, globalSettings));
}
QMapIterator<quint8, QJsonObject> i (instancesNewConfigs);
while (i.hasNext()) {
i.next();
quint8 idx = i.key();
Hyperion* instance = HyperionIManager::getInstance()->getHyperionInstance(idx);
QPair<bool, QStringList> isSaved = instance->saveSettings(i.value());
errorDetails.append(isSaved.second);
}
if (!errorDetails.isEmpty())
{
sendErrorReply("Update configuration failed", errorDetails, cmd);
return;
}
sendSuccessReply(cmd);
return;
}
if (config.isEmpty())
{
sendErrorReply("Update configuration failed", {"No configuration data provided!"}, cmd);
return;
}
//Backward compatability until UI mesages are updated
if (API::isHyperionEnabled())
{
QStringList errorDetails;
QPair<bool, QStringList> isSaved = _hyperion->saveSettings(config);
errorDetails.append(isSaved.second);
if (!errorDetails.isEmpty())
{
sendErrorReply("Save settings failed", errorDetails, cmd);
return;
}
sendSuccessReply(cmd);
}
else
{
sendErrorReply("Updating the configuration while Hyperion is disabled is not possible", cmd);
}
}
void JsonAPI::handleConfigGetCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
QJsonObject settings;
QStringList errorDetails;
QJsonObject filter = message["configFilter"].toObject();
if (!filter.isEmpty())
{
QStringList globalFilterTypes;
const QJsonObject globalConfig = filter["global"].toObject();
if (!globalConfig.isEmpty())
{
const QJsonArray globalTypes = globalConfig["types"].toArray();
for (const auto &type : globalTypes) {
if (type.isString()) {
globalFilterTypes.append(type.toString());
}
}
}
QList<quint8> instanceListFilter;
QStringList instanceFilterTypes;
const QJsonObject instances = filter["instances"].toObject();
if (!instances.isEmpty())
{
QList<quint8> configuredInstanceIds = _instanceManager->getInstanceIds();
const QJsonArray instanceIds = instances["ids"].toArray();
for (const auto &idx : instanceIds) {
if (idx.isDouble()) {
quint8 instanceId = static_cast<quint8>(idx.toInt());
if (configuredInstanceIds.contains(instanceId))
{
instanceListFilter.append(instanceId);
}
else
{
errorDetails.append(QString("Given instance number '%1' does not exist.").arg(instanceId));
}
}
}
const QJsonArray instanceTypes = instances["types"].toArray();
for (const auto &type : instanceTypes) {
if (type.isString()) {
instanceFilterTypes.append(type.toString());
}
}
}
settings = JsonInfo::getConfiguration(instanceListFilter, instanceFilterTypes, globalFilterTypes);
}
else
{
//Get complete configuration
settings = JsonInfo::getConfiguration();
}
if (!settings.empty())
{
sendSuccessDataReplyWithError(settings, cmd, errorDetails);
}
else
{
sendErrorReply("Generating full config failed", cmd);
}
}
void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
if (message.contains("config"))
QJsonObject config = message["config"].toObject();
if (API::isHyperionEnabled())
{
QJsonObject config = message["config"].toObject();
if (API::isHyperionEnabled())
DBConfigManager configManager;
QPair<bool, QStringList> result = configManager.updateConfiguration(config, false);
if (result.first)
{
if ( API::restoreSettings(config) )
{
sendSuccessReply(cmd);
}
else
{
sendErrorReply("Restore settings failed", cmd);
}
QString infoMsg {"Restarting after importing configuration successfully."};
sendSuccessDataReply(infoMsg, cmd);
Info(_log, "%s", QSTRING_CSTR(infoMsg));
emit signalEvent(Event::Restart);
}
else
{
sendErrorReply("Restoring configuration while Hyperion is disabled is not possible", cmd);
sendErrorReply("Restore configuration failed", result.second, cmd);
}
}
else
{
sendErrorReply("Restoring configuration while Hyperion is disabled is not possible", cmd);
}
}
void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd)
@@ -774,7 +927,7 @@ void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const JsonA
Q_INIT_RESOURCE(resource);
// read the hyperion json schema from the resource
QString schemaFile = ":/hyperion-schema";
QString schemaFile = SETTINGS_UI_SCHEMA_FILE;
try
{
@@ -1156,7 +1309,7 @@ void JsonAPI::handleInstanceCommand(const QJsonObject &message, const JsonApiCom
{
QString replyMsg;
const quint8 &inst = static_cast<quint8>(message["instance"].toInt());
const quint8 inst = static_cast<quint8>(message["instance"].toInt());
const QString &name = message["name"].toString();
switch (cmd.subCommand) {
@@ -1191,7 +1344,6 @@ void JsonAPI::handleInstanceCommand(const QJsonObject &message, const JsonApiCom
break;
case SubCommand::DeleteInstance:
handleConfigRestoreCommand(message, cmd);
if (API::deleteInstance(inst, replyMsg))
{
sendSuccessReply(cmd);
@@ -1213,7 +1365,7 @@ void JsonAPI::handleInstanceCommand(const QJsonObject &message, const JsonApiCom
if (cmd.subCommand == SubCommand::CreateInstance) {
replyMsg = API::createInstance(name);
} else {
replyMsg = setInstanceName(inst, name);
replyMsg = API::setInstanceName(inst, name);
}
if (replyMsg.isEmpty()) {

View File

@@ -1,3 +1,4 @@
#include <db/DBConfigManager.h>
#include <api/JsonInfo.h>
#include <api/API.h>
@@ -498,8 +499,8 @@ QJsonObject JsonInfo::getSystemInfo(const Hyperion* hyperion)
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();
hyperionInfo["configDatabaseFile"] = DBManager::getFileInfo().absoluteFilePath();
hyperionInfo["readOnlyMode"] = DBManager::isReadOnly();
QCoreApplication* app = QCoreApplication::instance();
hyperionInfo["isGuiMode"] = qobject_cast<QApplication*>(app) != nullptr;
@@ -622,3 +623,9 @@ QJsonArray JsonInfo::discoverScreenInputs(const QJsonObject& params) const
return screenInputs;
}
QJsonObject JsonInfo::getConfiguration(const QList<quint8>& instancesfilter, const QStringList& instanceFilteredTypes, const QStringList& globalFilterTypes )
{
DBConfigManager configManager;
return configManager.getConfiguration(instancesfilter, instanceFilteredTypes, globalFilterTypes );
}