diff --git a/daemon/extension/Extension.cpp b/daemon/extension/Extension.cpp index e6ab2bbda..a70982e9c 100644 --- a/daemon/extension/Extension.cpp +++ b/daemon/extension/Extension.cpp @@ -256,6 +256,9 @@ namespace Extension optionJson["Name"] = option.name; optionJson["DisplayName"] = option.displayName; + optionJson["Section"] = option.section.name; + optionJson["Multi"] = option.section.multi; + optionJson["Prefix"] = option.section.prefix; if (const std::string* val = boost::variant2::get_if(&option.value)) { @@ -296,7 +299,9 @@ namespace Extension commandJson["Name"] = command.name; commandJson["DisplayName"] = command.displayName; commandJson["Action"] = command.action; - + commandJson["Section"] = command.section.name; + commandJson["Multi"] = command.section.multi; + commandJson["Prefix"] = command.section.prefix; for (const auto& line : command.description) { @@ -358,6 +363,9 @@ namespace Extension AddNewNode(commandsNode, "Name", "string", command.name.c_str()); AddNewNode(commandsNode, "DisplayName", "string", command.displayName.c_str()); AddNewNode(commandsNode, "Action", "string", command.action.c_str()); + AddNewNode(commandsNode, "Multi", "boolean", BoolToStr(command.section.multi)); + AddNewNode(commandsNode, "Section", "string", command.section.name.c_str()); + AddNewNode(commandsNode, "Prefix", "string", command.section.prefix.c_str()); xmlNodePtr descriptionNode = xmlNewNode(NULL, BAD_CAST "Description"); for (const std::string& line : command.description) @@ -372,6 +380,9 @@ namespace Extension { AddNewNode(optionsNode, "Name", "string", option.name.c_str()); AddNewNode(optionsNode, "DisplayName", "string", option.displayName.c_str()); + AddNewNode(optionsNode, "Multi", "boolean", BoolToStr(option.section.multi)); + AddNewNode(optionsNode, "Section", "string", option.section.name.c_str()); + AddNewNode(optionsNode, "Prefix", "string", option.section.prefix.c_str()); if (const std::string* val = boost::variant2::get_if(&option.value)) { diff --git a/daemon/extension/Extension.h b/daemon/extension/Extension.h index dd4f04078..195579661 100644 --- a/daemon/extension/Extension.h +++ b/daemon/extension/Extension.h @@ -107,8 +107,8 @@ namespace Extension std::string ToJsonStr(const Script& script); std::string ToXmlStr(const Script& script); - static void AddNewNode(xmlNodePtr rootNode, const char* name, const char* type, const char* value); - static const char* BoolToStr(bool value); + void AddNewNode(xmlNodePtr rootNode, const char* name, const char* type, const char* value); + const char* BoolToStr(bool value); } #endif diff --git a/daemon/extension/ExtensionLoader.cpp b/daemon/extension/ExtensionLoader.cpp index 5ade183f8..94b40a386 100644 --- a/daemon/extension/ExtensionLoader.cpp +++ b/daemon/extension/ExtensionLoader.cpp @@ -27,6 +27,7 @@ namespace ExtensionLoader { + const char* DEFAULT_SECTION_NAME = "options"; const char* BEGIN_SCRIPT_SIGNATURE = "### NZBGET "; const char* BEGIN_SCRIPT_COMMANDS_AND_OTPIONS = "### OPTIONS"; const char* POST_SCRIPT_SIGNATURE = "POST-PROCESSING"; @@ -83,10 +84,12 @@ namespace ExtensionLoader { continue; } + if (!inBeforeConfig && !strncmp(line.c_str(), DEFINITION_SIGNATURE, DEFINITION_SIGNATURE_LEN)) { inBeforeConfig = true; } + if (!inBeforeConfig && !inConfig) { continue; @@ -149,6 +152,7 @@ namespace ExtensionLoader continue; } + // if "OPTIONS" and other sections, e.g.: ### OPTIONS or ### CATEGORIES if (!strncmp(line.c_str(), BEGIN_SCRIPT_COMMANDS_AND_OTPIONS, BEGIN_SCRIPT_COMMANDS_AND_OTPIONS_LEN)) { ParseOptionsAndCommands(file, options, commands); @@ -209,6 +213,7 @@ namespace ExtensionLoader { std::vector selectOpts; std::vector description; + std::string currSectionName = DEFAULT_SECTION_NAME; std::string line; while (std::getline(file, line)) @@ -223,6 +228,13 @@ namespace ExtensionLoader continue; } + if (!strncmp(line.c_str(), DEFINITION_SIGNATURE, DEFINITION_SIGNATURE_LEN)) + { + currSectionName = line.substr(DEFINITION_SIGNATURE_LEN + 1); + RemoveTailAndTrim(currSectionName, "###"); + continue; + } + size_t selectStartIdx = line.rfind("("); size_t selectEndIdx = line.rfind(")"); bool hasSelectOptions = description.empty() @@ -274,15 +286,11 @@ namespace ExtensionLoader if (atPos != std::string::npos && eqPos == std::string::npos) { - ManifestFile::Command command; - std::string name = line.substr(1, atPos - 1); - std::string action = line.substr(atPos + 1); - Util::Trim(action); - Util::Trim(name); - command.action = std::move(action); - command.name = std::move(name); + ManifestFile::Command command{}; + command.action = line.substr(atPos + 1); + Util::Trim(command.action); + ParseSectionAndSet(command, currSectionName, line, atPos); command.description = std::move(description); - command.displayName = command.name; commands.push_back(std::move(command)); description.clear(); selectOpts.clear(); @@ -291,17 +299,14 @@ namespace ExtensionLoader if (eqPos != std::string::npos) { - ManifestFile::Option option; - std::string name = line.substr(1, eqPos - 1); + ManifestFile::Option option{}; + ParseSectionAndSet(option, currSectionName, line, eqPos); + bool canBeNum = !selectOpts.empty() && boost::variant2::get_if(&selectOpts[0]); std::string value = line.substr(eqPos + 1); Util::Trim(value); - Util::Trim(name); - bool canBeNum = !selectOpts.empty() && boost::variant2::get_if(&selectOpts[0]); - option.value = std::move(GetSelectOpt(value, canBeNum)); - option.name = std::move(name); + option.value = GetSelectOpt(value, canBeNum); option.description = std::move(description); option.select = std::move(selectOpts); - option.displayName = option.name; options.push_back(std::move(option)); description.clear(); selectOpts.clear(); @@ -421,7 +426,7 @@ namespace ExtensionLoader bool V2::Load(Extension::Script& script, const char* location, const char* rootDir) { - ManifestFile::Manifest manifest; + ManifestFile::Manifest manifest{}; if (!ManifestFile::Load(manifest, location)) return false; diff --git a/daemon/extension/ExtensionLoader.h b/daemon/extension/ExtensionLoader.h index 916e8c626..07c5612d7 100644 --- a/daemon/extension/ExtensionLoader.h +++ b/daemon/extension/ExtensionLoader.h @@ -26,6 +26,7 @@ namespace ExtensionLoader { + extern const char* DEFAULT_SECTION_NAME; extern const char* BEGIN_SCRIPT_SIGNATURE; extern const char* BEGIN_SCRIPT_COMMANDS_AND_OTPIONS; extern const char* POST_SCRIPT_SIGNATURE; @@ -48,18 +49,39 @@ namespace ExtensionLoader { bool Load(Extension::Script& script, const char* location, const char* rootDir); - static void ParseOptionsAndCommands( + void ParseOptionsAndCommands( std::ifstream& file, std::vector& options, std::vector& commands ); - static std::vector + std::vector GetSelectOptions(const std::vector& opts, bool isDashDelim); - static ManifestFile::SelectOption GetSelectOpt(const std::string& val, bool canBeNum); - static void RemoveTailAndTrim(std::string& str, const char* tail); - static void BuildDisplayName(Extension::Script& script); - static std::pair, std::string> + ManifestFile::SelectOption GetSelectOpt(const std::string& val, bool canBeNum); + void RemoveTailAndTrim(std::string& str, const char* tail); + void BuildDisplayName(Extension::Script& script); + std::pair, std::string> ExtractElements(const std::string& str); + template + void ParseSectionAndSet(T& opt, std::string sectionName, const std::string& line, size_t sepPos) + { + opt.name = line.substr(1, sepPos - 1); + Util::Trim(opt.name); + + ManifestFile::Section section{}; + section.name = std::move(sectionName); + + size_t digitPos = opt.name.find("1."); + if (digitPos != std::string::npos) + { + section.prefix = opt.name.substr(0, digitPos); + section.multi = true; + opt.name = opt.name.substr(digitPos + 2); + + } + + opt.section = std::move(section); + opt.displayName = opt.name; + } } namespace V2 @@ -67,7 +89,7 @@ namespace ExtensionLoader bool Load(Extension::Script& script, const char* location, const char* rootDir); } - static Extension::Kind GetScriptKind(const std::string& line); + Extension::Kind GetScriptKind(const std::string& line); } #endif diff --git a/daemon/extension/ManifestFile.cpp b/daemon/extension/ManifestFile.cpp index d96836c12..bac0c1266 100644 --- a/daemon/extension/ManifestFile.cpp +++ b/daemon/extension/ManifestFile.cpp @@ -27,6 +27,7 @@ namespace ManifestFile { const char* MANIFEST_FILE = "manifest.json"; + const char* DEFAULT_SECTION_NAME = "options"; bool Load(Manifest& manifest, const char* directory) { @@ -89,6 +90,8 @@ namespace ManifestFile if (!ValidateCommandsAndSet(json, manifest.commands)) return false; + ValidateSectionsAndSet(json, manifest.options, manifest.commands); + if (!ValidateTxtAndSet(json, manifest.description, "description")) return false; @@ -107,7 +110,9 @@ namespace ManifestFile for (auto& value : rawCommands->as_array()) { Json::JsonObject cmdJson = value.as_object(); - Command command; + Command command{}; + + CheckKeyAndSet(cmdJson, "section", command.section.name, DEFAULT_SECTION_NAME); if (!CheckKeyAndSet(cmdJson, "name", command.name)) continue; @@ -142,7 +147,10 @@ namespace ManifestFile if (!selectJson || !selectJson->is_array()) continue; - Option option; + Option option{}; + + CheckKeyAndSet(optionJson, "section", option.section.name, DEFAULT_SECTION_NAME); + if (!CheckKeyAndSet(optionJson, "name", option.name)) continue; @@ -206,6 +214,19 @@ namespace ManifestFile return true; } + bool CheckKeyAndSet(const Json::JsonObject& json, const char* key, std::string& property, std::string defValue) + { + const auto& rawProperty = json.if_contains(key); + if (!rawProperty || !rawProperty->is_string()) + { + property = std::move(defValue); + return false; + } + + property = rawProperty->get_string().c_str(); + return true; + } + bool CheckKeyAndSet(const Json::JsonObject& json, const char* key, SelectOption& property) { const auto& rawProperty = json.if_contains(key); @@ -226,4 +247,60 @@ namespace ManifestFile return false; } + + bool CheckKeyAndSet(const Json::JsonObject& json, const char* key, bool& property) + { + const auto& rawProperty = json.if_contains(key); + if (rawProperty && rawProperty->is_bool()) + { + property = rawProperty->as_bool(); + return true; + } + + return false; + } + + bool ValidateSectionsAndSet(const Json::JsonObject& json, std::vector