Skip to content

Commit

Permalink
Fix: extension dynamic options (#209)
Browse files Browse the repository at this point in the history
- fixed adding dynamic extension options in webui;
- resoloved several compiler warnings;
- added more unit tests;
- updated docs;
  • Loading branch information
dnzbk committed Mar 29, 2024
1 parent e3d4c4d commit d49dc6d
Show file tree
Hide file tree
Showing 17 changed files with 539 additions and 126 deletions.
13 changes: 12 additions & 1 deletion daemon/extension/Extension.cpp
Expand Up @@ -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<std::string>(&option.value))
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand All @@ -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<std::string>(&option.value))
{
Expand Down
4 changes: 2 additions & 2 deletions daemon/extension/Extension.h
Expand Up @@ -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
37 changes: 21 additions & 16 deletions daemon/extension/ExtensionLoader.cpp
Expand Up @@ -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";
Expand Down Expand Up @@ -83,10 +84,12 @@ namespace ExtensionLoader
{
continue;
}

if (!inBeforeConfig && !strncmp(line.c_str(), DEFINITION_SIGNATURE, DEFINITION_SIGNATURE_LEN))
{
inBeforeConfig = true;
}

if (!inBeforeConfig && !inConfig)
{
continue;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -209,6 +213,7 @@ namespace ExtensionLoader
{
std::vector<ManifestFile::SelectOption> selectOpts;
std::vector<std::string> description;
std::string currSectionName = DEFAULT_SECTION_NAME;

std::string line;
while (std::getline(file, line))
Expand All @@ -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()
Expand Down Expand Up @@ -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<ManifestFile::Command>(command, currSectionName, line, atPos);
command.description = std::move(description);
command.displayName = command.name;
commands.push_back(std::move(command));
description.clear();
selectOpts.clear();
Expand All @@ -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<ManifestFile::Option>(option, currSectionName, line, eqPos);
bool canBeNum = !selectOpts.empty() && boost::variant2::get_if<double>(&selectOpts[0]);
std::string value = line.substr(eqPos + 1);
Util::Trim(value);
Util::Trim(name);
bool canBeNum = !selectOpts.empty() && boost::variant2::get_if<double>(&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();
Expand Down Expand Up @@ -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;

Expand Down
36 changes: 29 additions & 7 deletions daemon/extension/ExtensionLoader.h
Expand Up @@ -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;
Expand All @@ -48,26 +49,47 @@ namespace ExtensionLoader
{
bool Load(Extension::Script& script, const char* location, const char* rootDir);

static void ParseOptionsAndCommands(
void ParseOptionsAndCommands(
std::ifstream& file,
std::vector<ManifestFile::Option>& options,
std::vector<ManifestFile::Command>& commands
);
static std::vector<ManifestFile::SelectOption>
std::vector<ManifestFile::SelectOption>
GetSelectOptions(const std::vector<std::string>& 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::vector<std::string>, 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::vector<std::string>, std::string>
ExtractElements(const std::string& str);
template <typename T>
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
{
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
81 changes: 79 additions & 2 deletions daemon/extension/ManifestFile.cpp
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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<Option>& options, std::vector<Command>& commands)
{
auto rawSections = json.if_contains("sections");
if (!rawSections || !rawSections->is_array())
return false;

for (auto& sectionVal : rawSections->as_array())
{
Json::JsonObject sectionJson = sectionVal.as_object();

Section section;

if (!CheckKeyAndSet(sectionJson, "name", section.name))
continue;

if (Util::StrCaseCmp(section.name, DEFAULT_SECTION_NAME))
continue;

if (!CheckKeyAndSet(sectionJson, "prefix", section.prefix))
continue;

if (!CheckKeyAndSet(sectionJson, "multi", section.multi))
continue;

for (auto& option : options)
{
if (option.section.name == section.name)
{
option.section = section;
}
}

for (auto& command : commands)
{
if (command.section.name == section.name)
{
command.section = section;
}
}
}

return true;
}
}
26 changes: 20 additions & 6 deletions daemon/extension/ManifestFile.h
Expand Up @@ -24,15 +24,25 @@
#include <vector>
#include <boost/variant2.hpp>
#include "Json.h"
#include "Util.h"

namespace ManifestFile
{
using SelectOption = boost::variant2::variant<double, std::string>;

extern const char* MANIFEST_FILE;
extern const char* DEFAULT_SECTION_NAME;

struct Section
{
bool multi;
std::string name;
std::string prefix;
};

struct Option
{
Section section;
std::string name;
std::string displayName;
std::vector<std::string> description;
Expand All @@ -42,6 +52,7 @@ namespace ManifestFile

struct Command
{
Section section;
std::string name;
std::string displayName;
std::string action;
Expand Down Expand Up @@ -69,12 +80,15 @@ namespace ManifestFile

bool Load(Manifest& manifest, const char* directory);

static bool ValidateAndSet(const Json::JsonObject& json, Manifest& manifest);
static bool ValidateCommandsAndSet(const Json::JsonObject& json, std::vector<Command>& commands);
static bool ValidateOptionsAndSet(const Json::JsonObject& json, std::vector<Option>& options);
static bool ValidateTxtAndSet(const Json::JsonObject& json, std::vector<std::string>& property, const char* propName);
static bool CheckKeyAndSet(const Json::JsonObject& json, const char* key, std::string& property);
static bool CheckKeyAndSet(const Json::JsonObject& json, const char* key, SelectOption& property);
bool ValidateAndSet(const Json::JsonObject& json, Manifest& manifest);
bool ValidateCommandsAndSet(const Json::JsonObject& json, std::vector<Command>& commands);
bool ValidateOptionsAndSet(const Json::JsonObject& json, std::vector<Option>& options);
bool ValidateSectionsAndSet(const Json::JsonObject& json, std::vector<Option>& options, std::vector<Command>& commands);
bool ValidateTxtAndSet(const Json::JsonObject& json, std::vector<std::string>& property, const char* propName);
bool CheckKeyAndSet(const Json::JsonObject& json, const char* key, std::string& property);
bool CheckKeyAndSet(const Json::JsonObject& json, const char* key, std::string& property, std::string defValue);
bool CheckKeyAndSet(const Json::JsonObject& json, const char* key, SelectOption& property);
bool CheckKeyAndSet(const Json::JsonObject& json, const char* key, bool& property);
};

#endif

0 comments on commit d49dc6d

Please sign in to comment.