From 68fd39567013ec86785e021b310cc73278d40e69 Mon Sep 17 00:00:00 2001 From: Paulchen-Panther Date: Sun, 14 Aug 2016 20:17:12 +0200 Subject: [PATCH] JSON RPC Writer (configSet) (#175) * Remove "endOfJson" Value Deprecated value from Hypercon * Remove "endOfJson" Value Deprecated value from Hypercon * Add writeJson function to JsonFactory * ability to ignore required value in schema file * Remove "endOfJson" Value * Add handleConfigSetCommand function * Add handleConfigSetCommand function * Update JsonSchemas.qrc * Update schema.json * Update JsonSchemaChecker.cpp * Add configSet command to Hyperion-remote * Add setConfigFile function * Add setConfigFile function * Add schema-configset.json --- config/hyperion.config.json.commented | 4 +- config/hyperion.config.json.default | 4 +- include/utils/jsonschema/JsonFactory.h | 8 +++ include/utils/jsonschema/JsonSchemaChecker.h | 4 +- libsrc/hyperion/hyperion.schema.json | 4 -- libsrc/jsonserver/JsonClientConnection.cpp | 54 ++++++++++++++++++- libsrc/jsonserver/JsonClientConnection.h | 10 +++- libsrc/jsonserver/JsonSchemas.qrc | 1 + .../jsonserver/schema/schema-configset.json | 20 +++++++ libsrc/jsonserver/schema/schema.json | 2 +- libsrc/utils/jsonschema/JsonSchemaChecker.cpp | 5 +- src/hyperion-remote/JsonConnection.cpp | 23 ++++++++ src/hyperion-remote/JsonConnection.h | 8 +++ src/hyperion-remote/hyperion-remote.cpp | 11 +++- 14 files changed, 138 insertions(+), 20 deletions(-) create mode 100644 libsrc/jsonserver/schema/schema-configset.json diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index 3b689754..c6c041a1 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -604,7 +604,5 @@ "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } } - ], - - "endOfJson" : "endOfJson" + ] } diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 23fd3638..9079295c 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -437,7 +437,5 @@ "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } } - ], - - "endOfJson" : "endOfJson" + ] } diff --git a/include/utils/jsonschema/JsonFactory.h b/include/utils/jsonschema/JsonFactory.h index 25774200..319d7169 100644 --- a/include/utils/jsonschema/JsonFactory.h +++ b/include/utils/jsonschema/JsonFactory.h @@ -63,4 +63,12 @@ public: } return jsonTree; } + + static void writeJson(const std::string& filename, Json::Value& jsonTree) + { + Json::StyledStreamWriter writer; + + std::ofstream ofs(filename.c_str()); + writer.write(ofs, jsonTree); + } }; diff --git a/include/utils/jsonschema/JsonSchemaChecker.h b/include/utils/jsonschema/JsonSchemaChecker.h index d03a11e5..a6f4ebd7 100644 --- a/include/utils/jsonschema/JsonSchemaChecker.h +++ b/include/utils/jsonschema/JsonSchemaChecker.h @@ -43,7 +43,7 @@ public: /// @param value The JSON value to check /// @return true when the arguments is valid according to the schema /// - bool validate(const Json::Value & value); + bool validate(const Json::Value & value, bool ignoreRequired = false); /// /// @return A list of error messages @@ -179,6 +179,8 @@ private: private: /// The schema of the entire json-configuration Json::Value _schema; + /// ignore the required value in json schema + bool _ignoreRequired; /// The current location into a json-configuration structure being checked std::list _currentPath; diff --git a/libsrc/hyperion/hyperion.schema.json b/libsrc/hyperion/hyperion.schema.json index 54e27711..3416e866 100644 --- a/libsrc/hyperion/hyperion.schema.json +++ b/libsrc/hyperion/hyperion.schema.json @@ -896,10 +896,6 @@ }, "additionalProperties" : false } - }, - "endOfJson" : - { - "type" : "string" } }, "additionalProperties" : false diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index 843c61bb..62212b2f 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -24,6 +24,7 @@ #include #include #include +#include // project includes #include "JsonClientConnection.h" @@ -267,6 +268,8 @@ void JsonClientConnection::handleMessage(const std::string &messageString) handleSourceSelectCommand(message); else if (command == "configget") handleConfigGetCommand(message); + else if (command == "configset") + handleConfigSetCommand(message); else if (command == "componentstate") handleComponentStateCommand(message); else @@ -842,6 +845,53 @@ void JsonClientConnection::handleConfigGetCommand(const Json::Value &) sendMessage(result); } +void JsonClientConnection::handleConfigSetCommand(const Json::Value &message) +{ + struct nested + { + static void configSetCommand(const Json::Value& message, Json::Value& config, bool& create) + { + if (!config.isObject() || !message.isObject()) + return; + + for (const auto& key : message.getMemberNames()) { + if ((config.isObject() && config.isMember(key)) || create) + { + if (config[key].type() == Json::objectValue && message[key].type() == Json::objectValue) + { + configSetCommand(message[key], config[key], create); + } + else + if ( !config[key].empty() || create) + config[key] = message[key]; + } + } + } + }; + + if(message.size() > 0) + { + if (message.isObject() && message.isMember("configset")) + { + std::string errors; + if (!checkJson(message["configset"], ":/hyperion-schema", errors, true)) + { + sendErrorReply("Error while validating json: " + errors); + return; + } + + bool createKey = message.isMember("create"); + Json::Value hyperionConfig = _hyperion->getJsonConfig(); + nested::configSetCommand(message["configset"], hyperionConfig, createKey); + + JsonFactory::writeJson(_hyperion->getConfigFileName(), hyperionConfig); + + sendSuccessReply(); + } + } else + sendErrorReply("Error while parsing json: Message size " + message.size()); +} + void JsonClientConnection::handleComponentStateCommand(const Json::Value& message) { const Json::Value & componentState = message["componentstate"]; @@ -956,7 +1006,7 @@ void JsonClientConnection::sendErrorReply(const std::string &error) sendMessage(reply); } -bool JsonClientConnection::checkJson(const Json::Value & message, const QString & schemaResource, std::string & errorMessage) +bool JsonClientConnection::checkJson(const Json::Value & message, const QString & schemaResource, std::string & errorMessage, bool ignoreRequired) { // read the json schema from the resource QResource schemaData(schemaResource); @@ -974,7 +1024,7 @@ bool JsonClientConnection::checkJson(const Json::Value & message, const QString schema.setSchema(schemaJson); // check the message - if (!schema.validate(message)) + if (!schema.validate(message, ignoreRequired)) { const std::list & errors = schema.getMessages(); std::stringstream ss; diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index 0362e94c..c49c21b8 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -144,6 +144,11 @@ private: /// @param message the incoming message /// void handleConfigGetCommand(const Json::Value & message); + + /// + /// Handle an incoming JSON SetConfig message + /// + void handleConfigSetCommand(const Json::Value & message); /// /// Handle an incoming JSON Component State message @@ -197,12 +202,13 @@ private: /// Check if a JSON messag is valid according to a given JSON schema /// /// @param message JSON message which need to be checked - /// @param schemaResource Qt esource identifier with the JSON schema + /// @param schemaResource Qt Resource identifier with the JSON schema /// @param errors Output error message + /// @param ignoreRequired ignore the required value in JSON schema /// /// @return true if message conforms the given JSON schema /// - bool checkJson(const Json::Value & message, const QString &schemaResource, std::string & errors); + bool checkJson(const Json::Value & message, const QString &schemaResource, std::string & errors, bool ignoreRequired = false); private: /// The TCP-Socket that is connected tot the Json-client diff --git a/libsrc/jsonserver/JsonSchemas.qrc b/libsrc/jsonserver/JsonSchemas.qrc index 26129ddf..41f0b754 100644 --- a/libsrc/jsonserver/JsonSchemas.qrc +++ b/libsrc/jsonserver/JsonSchemas.qrc @@ -13,6 +13,7 @@ schema/schema-effect.json schema/schema-sourceselect.json schema/schema-configget.json + schema/schema-configset.json schema/schema-componentstate.json diff --git a/libsrc/jsonserver/schema/schema-configset.json b/libsrc/jsonserver/schema/schema-configset.json new file mode 100644 index 00000000..b4d21467 --- /dev/null +++ b/libsrc/jsonserver/schema/schema-configset.json @@ -0,0 +1,20 @@ +{ + "type" : "object", + "required" : true, + "properties" : { + "command": { + "type" : "string", + "required" : true, + "enum" : ["configset"] + }, + "configset": { + "type" : "object", + "required" : true + }, + "create": { + "type" : "boolean", + "required" : false + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/libsrc/jsonserver/schema/schema.json b/libsrc/jsonserver/schema/schema.json index c175990b..fde0009c 100644 --- a/libsrc/jsonserver/schema/schema.json +++ b/libsrc/jsonserver/schema/schema.json @@ -5,7 +5,7 @@ "command": { "type" : "string", "required" : true, - "enum" : ["color", "image", "effect", "serverinfo", "clear", "clearall", "transform", "correction", "temperature", "adjustment", "sourceselect", "configget", "componentstate"] + "enum" : ["color", "image", "effect", "serverinfo", "clear", "clearall", "transform", "correction", "temperature", "adjustment", "sourceselect", "configget", "configset", "componentstate"] } } } diff --git a/libsrc/utils/jsonschema/JsonSchemaChecker.cpp b/libsrc/utils/jsonschema/JsonSchemaChecker.cpp index 9a6a6116..24a2c00a 100644 --- a/libsrc/utils/jsonschema/JsonSchemaChecker.cpp +++ b/libsrc/utils/jsonschema/JsonSchemaChecker.cpp @@ -26,9 +26,10 @@ bool JsonSchemaChecker::setSchema(const Json::Value & schema) return true; } -bool JsonSchemaChecker::validate(const Json::Value & value) +bool JsonSchemaChecker::validate(const Json::Value & value, bool ignoreRequired) { // initialize state + _ignoreRequired = ignoreRequired; _error = false; _messages.clear(); _currentPath.clear(); @@ -201,7 +202,7 @@ void JsonSchemaChecker::checkProperties(const Json::Value & value, const Json::V { validate(value[property], propertyValue); } - else if (propertyValue.get("required", false).asBool()) + else if (propertyValue.get("required", false).asBool() && !_ignoreRequired) { _error = true; setMessage("missing member"); diff --git a/src/hyperion-remote/JsonConnection.cpp b/src/hyperion-remote/JsonConnection.cpp index fd5083bc..769ba0da 100644 --- a/src/hyperion-remote/JsonConnection.cpp +++ b/src/hyperion-remote/JsonConnection.cpp @@ -265,6 +265,29 @@ QString JsonConnection::getConfigFile() return QString(); } +void JsonConnection::setConfigFile(const std::string &jsonString, bool create) +{ + // create command + Json::Value command; + command["command"] = "configset"; + command["create"] = create; + Json::Value & config = command["configset"]; + if (jsonString.size() > 0) + { + Json::Reader reader; + if (!reader.parse(jsonString, config, false)) + { + throw std::runtime_error("Error in configset arguments: " + reader.getFormattedErrorMessages()); + } + } + + // send command message + Json::Value reply = sendMessage(command); + + // parse reply message + parseReply(reply); +} + void JsonConnection::setTransform(std::string * transformId, double * saturation, double * value, double * saturationL, double * luminance, double * luminanceMin, ColorTransformValues *threshold, ColorTransformValues *gamma, ColorTransformValues *blacklevel, ColorTransformValues *whitelevel) { std::cout << "Set color transforms" << std::endl; diff --git a/src/hyperion-remote/JsonConnection.h b/src/hyperion-remote/JsonConnection.h index ac6877e8..dee3d83c 100644 --- a/src/hyperion-remote/JsonConnection.h +++ b/src/hyperion-remote/JsonConnection.h @@ -108,6 +108,14 @@ public: /// QString getConfigFile(); + /// + /// Write JSON Value(s) to the actual loaded configuration file + /// + /// @param jsonString The JSON String(s) to write + /// @param create Specifies whether the nonexistent json string to be created + /// + void setConfigFile(const std::string & jsonString, bool create); + /// /// Set the color transform of the leds /// diff --git a/src/hyperion-remote/hyperion-remote.cpp b/src/hyperion-remote/hyperion-remote.cpp index 40e71aae..9331bc84 100644 --- a/src/hyperion-remote/hyperion-remote.cpp +++ b/src/hyperion-remote/hyperion-remote.cpp @@ -87,7 +87,9 @@ int main(int argc, char * argv[]) IntParameter & argSource = parameters.add (0x0, "sourceSelect" , "Set current active priority channel and deactivate auto source switching"); SwitchParameter<> & argSourceAuto = parameters.add >(0x0, "sourceAutoSelect", "Enables auto source, if disabled prio by manual selecting input source"); SwitchParameter<> & argSourceOff = parameters.add >(0x0, "sourceOff", "select no source, this results in leds activly set to black (=off)"); - SwitchParameter<> & argConfigGet = parameters.add >(0x0, "configget" , "Print the current loaded Hyperion configuration file"); + SwitchParameter<> & argConfigGet = parameters.add >(0x0, "configGet" , "Print the current loaded Hyperion configuration file"); + StringParameter & argConfigSet = parameters.add('W', "configSet", "Write to the actual loaded configuration file. Should be a Json object string."); + SwitchParameter<> & argCreate = parameters.add >(0x0, "createkeys", "Create non exist Json Entry(s) in the actual loaded configuration file. Argument to use in combination with configSet."); // set the default values argAddress.setDefault(defaultServerAddress.toStdString()); @@ -111,7 +113,7 @@ int main(int argc, char * argv[]) bool colorModding = colorTransform || colorAdjust || argCorrection.isSet() || argTemperature.isSet(); // check that exactly one command was given - int commandCount = count({argColor.isSet(), argImage.isSet(), argEffect.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), argEnableComponent.isSet(), argDisableComponent.isSet(), colorModding, argSource.isSet(), argSourceAuto.isSet(), argSourceOff.isSet(), argConfigGet.isSet()}); + int commandCount = count({argColor.isSet(), argImage.isSet(), argEffect.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), argEnableComponent.isSet(), argDisableComponent.isSet(), colorModding, argSource.isSet(), argSourceAuto.isSet(), argSourceOff.isSet(), argConfigGet.isSet(), argConfigSet.isSet()}); if (commandCount != 1) { std::cerr << (commandCount == 0 ? "No command found." : "Multiple commands found.") << " Provide exactly one of the following options:" << std::endl; @@ -126,6 +128,7 @@ int main(int argc, char * argv[]) std::cerr << " " << argSource.usageLine() << std::endl; std::cerr << " " << argSourceAuto.usageLine() << std::endl; std::cerr << " " << argConfigGet.usageLine() << std::endl; + std::cerr << " " << argConfigSet.usageLine() << std::endl; std::cerr << "or one or more of the available color modding operations:" << std::endl; std::cerr << " " << argId.usageLine() << std::endl; std::cerr << " " << argSaturation.usageLine() << std::endl; @@ -202,6 +205,10 @@ int main(int argc, char * argv[]) QString info = connection.getConfigFile(); std::cout << "Configuration File:\n" << info.toStdString() << std::endl; } + else if (argConfigSet.isSet()) + { + connection.setConfigFile(argConfigSet.getValue(), argCreate.isSet()); + } else if (colorModding) { if (argCorrection.isSet())