Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: extension dynamic options #209

Merged
merged 33 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion daemon/extension/Extension.cpp
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading