Files
fluxengine/lib/config.cc
2023-05-15 20:55:33 +00:00

674 lines
19 KiB
C++

#include "lib/globals.h"
#include "lib/config.h"
#include "lib/proto.h"
#include "lib/logger.h"
#include "lib/utils.h"
#include "lib/imagewriter/imagewriter.h"
#include "lib/imagereader/imagereader.h"
#include "lib/fluxsink/fluxsink.h"
#include "lib/fluxsource/fluxsource.h"
#include "lib/encoders/encoders.h"
#include "lib/decoders/decoders.h"
#include <fstream>
#include <google/protobuf/text_format.h>
#include <regex>
static Config config;
Config& globalConfig()
{
return config;
}
ConfigProto* Config::combined()
{
if (!_configValid)
{
_combinedConfig = _baseConfig;
/* First apply any standalone options. */
std::set<std::string> options = _appliedOptions;
std::set<const OptionRequirementProto*> requirements;
for (const auto& option : _baseConfig.option())
{
if (options.find(option.name()) != options.end())
{
_combinedConfig.MergeFrom(option.config());
options.erase(option.name());
}
}
/* Then apply any group options. */
for (auto& group : _baseConfig.option_group())
{
const OptionProto* selectedOption = &*group.option().begin();
for (auto& option : group.option())
{
if (options.find(option.name()) != options.end())
{
selectedOption = &option;
options.erase(option.name());
}
}
_combinedConfig.MergeFrom(selectedOption->config());
}
/* Add in the user overrides. */
_combinedConfig.MergeFrom(_overridesConfig);
/* At this point the config is mostly valid. We're about to make calls
* that will want to call combined() reentrantly, so to prevent infinite
* loop we mark the config as valid now. */
_configValid = true;
/* We should now be more or less done, but we still need to add in any
* config contributed by the flux source and image readers. This will
* open the files. */
if (hasFluxSource())
_combinedConfig.MergeFrom(getFluxSource()->getExtraConfig());
if (hasImageReader())
_combinedConfig.MergeFrom(getImageReader()->getExtraConfig());
/* Merge in the overrides once again. */
_combinedConfig.MergeFrom(_overridesConfig);
}
return &_combinedConfig;
}
void Config::invalidate()
{
_configValid = false;
}
void Config::clear()
{
_configValid = false;
_baseConfig.Clear();
_overridesConfig.Clear();
_combinedConfig.Clear();
_fluxSource.reset();
_verificationFluxSource.reset();
_imageReader.reset();
_encoder.reset();
_decoder.reset();
_appliedOptions.clear();
}
std::vector<std::string> Config::validate()
{
std::vector<std::string> results;
std::set<std::string> optionNames = _appliedOptions;
std::set<const OptionProto*> appliedOptions;
for (const auto& option : _baseConfig.option())
{
if (optionNames.find(option.name()) != optionNames.end())
{
appliedOptions.insert(&option);
optionNames.erase(option.name());
}
}
/* Then apply any group options. */
for (auto& group : _baseConfig.option_group())
{
int count = 0;
for (auto& option : group.option())
{
if (optionNames.find(option.name()) != optionNames.end())
{
optionNames.erase(option.name());
appliedOptions.insert(&option);
count++;
if (count == 2)
results.push_back(
fmt::format("multiple mutually exclusive options set "
"for group '{}'",
group.comment()));
}
}
}
/* Check for unknown options. */
if (!optionNames.empty())
{
for (auto& name : optionNames)
results.push_back(fmt::format("'{}' is not a known option", name));
}
/* Check option requirements. */
for (auto& option : appliedOptions)
{
try
{
checkOptionValid(*option);
}
catch (const InapplicableOptionException& e)
{
results.push_back(e.message);
}
}
return results;
}
void Config::validateAndThrow()
{
auto r = validate();
if (!r.empty())
{
std::stringstream ss;
ss << "invalid configuration:\n";
for (auto& s : r)
ss << s << '\n';
throw InapplicableOptionException(ss.str());
}
}
void Config::set(std::string key, std::string value)
{
setProtoByString(overrides(), key, value);
}
void Config::setTransient(std::string key, std::string value)
{
setProtoByString(&_combinedConfig, key, value);
}
std::string Config::get(std::string key)
{
return getProtoByString(combined(), key);
}
static ConfigProto loadSingleConfigFile(std::string filename)
{
const auto& it = formats.find(filename);
if (it != formats.end())
return *it->second;
else
{
std::ifstream f(filename, std::ios::out);
if (f.fail())
error("Cannot open '{}': {}", filename, strerror(errno));
std::ostringstream ss;
ss << f.rdbuf();
ConfigProto config;
if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config))
error("couldn't load external config proto");
return config;
}
}
void Config::readBaseConfigFile(std::string filename)
{
base()->MergeFrom(loadSingleConfigFile(filename));
}
void Config::readBaseConfig(std::string data)
{
if (!google::protobuf::TextFormat::MergeFromString(data, base()))
error("couldn't load external config proto");
}
const OptionProto& Config::findOption(const std::string& optionName)
{
const OptionProto* found = nullptr;
auto searchOptionList = [&](auto& optionList)
{
for (const auto& option : optionList)
{
if (optionName == option.name())
{
found = &option;
return true;
}
}
return false;
};
if (searchOptionList(base()->option()))
return *found;
for (const auto& optionGroup : base()->option_group())
{
if (searchOptionList(optionGroup.option()))
return *found;
}
throw OptionNotFoundException(
fmt::format("option {} not found", optionName));
}
void Config::checkOptionValid(const OptionProto& option)
{
for (const auto& req : option.requires())
{
bool matched = false;
try
{
auto value = get(req.key());
for (auto requiredValue : req.value())
matched |= (requiredValue == value);
}
catch (const ProtoPathNotFoundException e)
{
/* This field isn't available, therefore it
* cannot match. */
}
if (!matched)
{
std::stringstream ss;
ss << '[';
bool first = true;
for (auto requiredValue : req.value())
{
if (!first)
ss << ", ";
ss << quote(requiredValue);
first = false;
}
ss << ']';
throw InapplicableOptionException(
fmt::format("option '{}' is inapplicable to this "
"configuration "
"because {}={} could not be met",
option.name(),
req.key(),
ss.str()));
}
}
}
bool Config::isOptionValid(const OptionProto& option)
{
try
{
checkOptionValid(option);
return true;
}
catch (const InapplicableOptionException& e)
{
return false;
}
}
bool Config::isOptionValid(std::string option)
{
return isOptionValid(findOption(option));
}
void Config::applyOption(const OptionProto& option)
{
log("OPTION: {}",
option.has_message() ? option.message() : option.comment());
_appliedOptions.insert(option.name());
}
void Config::applyOption(std::string option)
{
applyOption(findOption(option));
}
void Config::clearOptions()
{
_appliedOptions.clear();
invalidate();
}
static void setFluxSourceImpl(std::string filename, FluxSourceProto* proto)
{
static const std::vector<std::pair<std::regex,
std::function<void(const std::string&, FluxSourceProto*)>>>
formats = {
{std::regex("^(.*\\.flux)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::FLUX);
proto->mutable_fl2()->set_filename(s);
}},
{std::regex("^(.*\\.scp)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::SCP);
proto->mutable_scp()->set_filename(s);
}},
{std::regex("^(.*\\.a2r)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::A2R);
proto->mutable_a2r()->set_filename(s);
}},
{std::regex("^(.*\\.cwf)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::CWF);
proto->mutable_cwf()->set_filename(s);
}},
{std::regex("^erase:$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::ERASE);
}},
{std::regex("^kryoflux:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::KRYOFLUX);
proto->mutable_kryoflux()->set_directory(s);
}},
{std::regex("^testpattern:(.*)"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::TEST_PATTERN);
}},
{std::regex("^drive:(.*)"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::DRIVE);
globalConfig().overrides()->mutable_drive()->set_drive(
std::stoi(s));
}},
{std::regex("^flx:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::FLX);
proto->mutable_flx()->set_directory(s);
}},
};
for (const auto& it : formats)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1], proto);
return;
}
}
error("unrecognised flux filename '{}'", filename);
}
void Config::setFluxSource(std::string filename)
{
setFluxSourceImpl(filename, overrides()->mutable_flux_source());
}
static void setFluxSinkImpl(std::string filename, FluxSinkProto* proto)
{
static const std::vector<std::pair<std::regex,
std::function<void(const std::string&, FluxSinkProto*)>>>
formats = {
{std::regex("^(.*\\.a2r)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::A2R);
proto->mutable_a2r()->set_filename(s);
}},
{std::regex("^(.*\\.flux)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::FLUX);
proto->mutable_fl2()->set_filename(s);
}},
{std::regex("^(.*\\.scp)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::SCP);
proto->mutable_scp()->set_filename(s);
}},
{std::regex("^vcd:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::VCD);
proto->mutable_vcd()->set_directory(s);
}},
{std::regex("^au:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::AU);
proto->mutable_au()->set_directory(s);
}},
{std::regex("^drive:(.*)"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::DRIVE);
globalConfig().overrides()->mutable_drive()->set_drive(
std::stoi(s));
}},
};
for (const auto& it : formats)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1], proto);
return;
}
}
error("unrecognised flux filename '{}'", filename);
}
void Config::setFluxSink(std::string filename)
{
setFluxSinkImpl(filename, overrides()->mutable_flux_sink());
}
void Config::setCopyFluxTo(std::string filename)
{
setFluxSinkImpl(
filename, overrides()->mutable_decoder()->mutable_copy_flux_to());
}
void Config::setVerificationFluxSource(std::string filename)
{
setFluxSourceImpl(filename, &_verificationFluxSourceProto);
}
void Config::setImageReader(std::string filename)
{
static const std::map<std::string, std::function<void(ImageReaderProto*)>>
formats = {
// clang-format off
{".adf", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".d64", [](auto* proto) { proto->set_type(ImageReaderProto::D64); }},
{".d81", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".d88", [](auto* proto) { proto->set_type(ImageReaderProto::D88); }},
{".dim", [](auto* proto) { proto->set_type(ImageReaderProto::DIM); }},
{".diskcopy", [](auto* proto) { proto->set_type(ImageReaderProto::DISKCOPY); }},
{".dsk", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".fdi", [](auto* proto) { proto->set_type(ImageReaderProto::FDI); }},
{".imd", [](auto* proto) { proto->set_type(ImageReaderProto::IMD); }},
{".img", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".jv3", [](auto* proto) { proto->set_type(ImageReaderProto::JV3); }},
{".nfd", [](auto* proto) { proto->set_type(ImageReaderProto::NFD); }},
{".nsi", [](auto* proto) { proto->set_type(ImageReaderProto::NSI); }},
{".st", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".td0", [](auto* proto) { proto->set_type(ImageReaderProto::TD0); }},
{".vgi", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".xdf", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
// clang-format on
};
for (const auto& it : formats)
{
if (endsWith(filename, it.first))
{
it.second(overrides()->mutable_image_reader());
overrides()->mutable_image_reader()->set_filename(filename);
return;
}
}
error("unrecognised image filename '{}'", filename);
}
void Config::setImageWriter(std::string filename)
{
static const std::map<std::string, std::function<void(ImageWriterProto*)>>
formats = {
// clang-format off
{".adf", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".d64", [](auto* proto) { proto->set_type(ImageWriterProto::D64); }},
{".d81", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".d88", [](auto* proto) { proto->set_type(ImageWriterProto::D88); }},
{".diskcopy", [](auto* proto) { proto->set_type(ImageWriterProto::DISKCOPY); }},
{".dsk", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".img", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".imd", [](auto* proto) { proto->set_type(ImageWriterProto::IMD); }},
{".ldbs", [](auto* proto) { proto->set_type(ImageWriterProto::LDBS); }},
{".nsi", [](auto* proto) { proto->set_type(ImageWriterProto::NSI); }},
{".raw", [](auto* proto) { proto->set_type(ImageWriterProto::RAW); }},
{".st", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".vgi", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".xdf", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
// clang-format on
};
for (const auto& it : formats)
{
if (endsWith(filename, it.first))
{
it.second(overrides()->mutable_image_writer());
overrides()->mutable_image_writer()->set_filename(filename);
return;
}
}
error("unrecognised image filename '{}'", filename);
}
bool Config::hasFluxSource()
{
return (*this)->flux_source().type() != FluxSourceProto::NOT_SET;
}
std::shared_ptr<FluxSource>& Config::getFluxSource()
{
if (!_fluxSource)
{
if (!hasFluxSource())
error("no flux source configured");
_fluxSource =
std::shared_ptr(FluxSource::create((*this)->flux_source()));
}
return _fluxSource;
}
bool Config::hasVerificationFluxSource() const
{
return _verificationFluxSourceProto.type() != FluxSourceProto::NOT_SET;
}
std::shared_ptr<FluxSource>& Config::getVerificationFluxSource()
{
if (!_verificationFluxSource)
{
if (!hasVerificationFluxSource())
error("no verification flux source configured");
_verificationFluxSource =
std::shared_ptr(FluxSource::create(_verificationFluxSourceProto));
}
return _verificationFluxSource;
}
bool Config::hasImageReader()
{
return (*this)->image_reader().type() != ImageReaderProto::NOT_SET;
}
std::shared_ptr<ImageReader>& Config::getImageReader()
{
if (!_imageReader)
{
if (!hasImageReader())
error("no image reader configured");
_imageReader =
std::shared_ptr(ImageReader::create((*this)->image_reader()));
}
return _imageReader;
}
bool Config::hasFluxSink()
{
return (*this)->flux_sink().type() != FluxSinkProto::NOT_SET;
}
std::unique_ptr<FluxSink> Config::getFluxSink()
{
if (!hasFluxSink())
error("no flux sink configured");
return FluxSink::create((*this)->flux_sink());
}
bool Config::hasImageWriter()
{
return (*this)->image_writer().type() != ImageWriterProto::NOT_SET;
}
std::unique_ptr<ImageWriter> Config::getImageWriter()
{
if (!hasImageWriter())
error("no image writer configured");
return ImageWriter::create((*this)->image_writer());
}
bool Config::hasEncoder()
{
return (*this)->has_encoder();
}
std::shared_ptr<Encoder>& Config::getEncoder()
{
if (!_encoder)
{
if (!hasEncoder())
error("no encoder configured");
_encoder = Encoder::create((*this)->encoder());
}
return _encoder;
}
bool Config::hasDecoder()
{
return _combinedConfig.has_decoder();
}
std::shared_ptr<Decoder>& Config::getDecoder()
{
if (!_decoder)
{
if (!hasDecoder())
error("no decoder configured");
_decoder = Decoder::create((*this)->decoder());
}
return _decoder;
}