220 lines
5.4 KiB
C++
220 lines
5.4 KiB
C++
#include "objectnode.hpp"
|
|
|
|
#include <qjsonobject.h>
|
|
|
|
#include "codecs.hpp"
|
|
|
|
namespace ZShell::settings {
|
|
|
|
using Qt::StringLiterals::operator""_s;
|
|
|
|
ObjectNode::ObjectNode(ObjectNode* fallback, QObject* parent, bool globalOnly)
|
|
: Node(fallback, parent, globalOnly) {}
|
|
|
|
void ObjectNode::resetOption(const QString& key) {
|
|
const auto* desc = schema().get(key);
|
|
|
|
if (!desc) {
|
|
qCWarning(lcSettings)
|
|
<< "Attempted to reset unknown option" << pathFor(key);
|
|
return;
|
|
}
|
|
|
|
// Warn and ignore if this is a global node property and we are an overlay
|
|
if (desc->isNode && (m_globalOnly || desc->globalOnly()) &&
|
|
fallbackNode()) {
|
|
qCWarning(
|
|
lcSettings,
|
|
"Attempted to reset global node %s, ignoring. "
|
|
"This should not be used, reset global nodes from the global layer "
|
|
"instead.",
|
|
qUtf8Printable(pathFor(key)));
|
|
return;
|
|
}
|
|
|
|
const WriteScope scope(this, WriteOrigin::QmlReset);
|
|
if (desc->isNode)
|
|
value(key).value<Node*>()->resetToDefaults();
|
|
else
|
|
setValue(
|
|
key,
|
|
fallbackNode() ? fallbackNode()->value(key) : desc->defaultValue());
|
|
}
|
|
|
|
Descriptor ObjectNode::descriptorFor(const QString& key) const {
|
|
const auto* desc = schema().get(key);
|
|
if (!desc) {
|
|
qCWarning(lcSettings)
|
|
<< "Attempted to get descriptor for unknown option" << pathFor(key);
|
|
return {};
|
|
}
|
|
|
|
return *desc;
|
|
}
|
|
|
|
QJsonValue ObjectNode::toJson(bool sparse) const {
|
|
QJsonObject json;
|
|
|
|
for (const auto& desc : schema().descriptors()) {
|
|
const auto val = value(desc.key);
|
|
|
|
if (const auto* node = val.value<Node*>()) {
|
|
if (!sparse || node->hasContent())
|
|
json.insert(desc.key, node->toJson(sparse));
|
|
continue;
|
|
}
|
|
|
|
if (sparse && !isOverride(desc.key)) continue;
|
|
|
|
if (!desc.codec) { // This should not happen
|
|
qCCritical(
|
|
lcSettings,
|
|
"No codec found for type %s, not serialising %s",
|
|
desc.type.name(),
|
|
qUtf8Printable(pathFor(desc.key)));
|
|
continue;
|
|
}
|
|
|
|
json.insert(desc.key, desc.codec->encode(val));
|
|
}
|
|
|
|
if (m_quarantine) return m_quarantine->apply(json);
|
|
|
|
return json;
|
|
}
|
|
|
|
bool ObjectNode::syncJson(
|
|
const QJsonValue& json, QList<Diagnostic>& diagnostics) {
|
|
m_quarantine.reset(); // Clear out old quarantine
|
|
|
|
if (!json.isObject()) {
|
|
const auto d = Diagnostic::mismatch(ExpectedType::Object, json, path());
|
|
qCWarning(
|
|
lcSettings,
|
|
"Error decoding option %s: %s",
|
|
qUtf8Printable(d.option),
|
|
qUtf8Printable(d.message));
|
|
diagnostics << d;
|
|
return false;
|
|
}
|
|
|
|
// Refuse syncs to global only nodes on overlays
|
|
if (rejectGlobalSync(diagnostics)) return false;
|
|
|
|
const auto obj = json.toObject();
|
|
|
|
qCDebug(lcSettings) << "Loading JSON into" << metaObject()->className()
|
|
<< "with" << obj.size() << "keys:" << obj.keys();
|
|
|
|
const auto visited = loadFromJson(obj, diagnostics);
|
|
resetUnvisited(visited);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ObjectNode::quarantineKey(const QString& key, const QJsonValue& value) {
|
|
if (!m_quarantine) m_quarantine = std::make_unique<ObjectQuarantine>();
|
|
m_quarantine->insert(key, value);
|
|
}
|
|
|
|
QSet<QString> ObjectNode::loadFromJson(
|
|
const QJsonObject& json, QList<Diagnostic>& diagnostics) {
|
|
const WriteScope scope(this, WriteOrigin::File);
|
|
|
|
QSet<QString> visited;
|
|
visited.reserve(json.size());
|
|
|
|
// Convenience macro for quarantine then skip
|
|
#define SKIP \
|
|
quarantineKey(key, v); \
|
|
continue
|
|
|
|
for (const auto [k, v] : json.asKeyValueRange()) {
|
|
const auto key = k.toString();
|
|
const auto* desc = schema().get(key);
|
|
|
|
if (!desc) {
|
|
const auto path = pathFor(key);
|
|
qCWarning(lcSettings) << "Unknown option" << path;
|
|
diagnostics << Diagnostic{
|
|
.type = DiagnosticType::UnknownOption,
|
|
.option = path,
|
|
.message = QStringLiteral("Unknown option %1").arg(key),
|
|
};
|
|
SKIP;
|
|
}
|
|
|
|
// Recurse into child nodes
|
|
if (desc->isNode) {
|
|
qCDebug(lcSettings) << " Recursing into" << key;
|
|
auto* const node = value(key).value<Node*>();
|
|
if (node->syncJson(v, diagnostics))
|
|
visited << key;
|
|
else
|
|
quarantineKey(
|
|
key, v); // Quarantine entire node cause sync failed
|
|
continue;
|
|
}
|
|
|
|
if ((m_globalOnly || desc->globalOnly()) && fallbackNode()) {
|
|
warnGlobalSync(diagnostics, pathFor(key));
|
|
SKIP;
|
|
}
|
|
|
|
if (!desc->codec) { // This should not happen
|
|
qCCritical(
|
|
lcSettings,
|
|
"No codec found for type %s, not loading %s",
|
|
desc->type.name(),
|
|
qUtf8Printable(pathFor(key)));
|
|
SKIP;
|
|
}
|
|
|
|
auto val = desc->codec->decode(v);
|
|
if (val.error) {
|
|
auto path = pathFor(key);
|
|
for (const auto index : std::as_const(val.indexPath))
|
|
path = elementPath(path, QString::number(index));
|
|
qCWarning(
|
|
lcSettings,
|
|
"Error decoding option %s: %s",
|
|
qUtf8Printable(path),
|
|
qUtf8Printable(val.error->message));
|
|
val.error->option = path;
|
|
diagnostics << *val.error;
|
|
SKIP;
|
|
}
|
|
|
|
visited << key;
|
|
setValue(key, val.value);
|
|
}
|
|
|
|
#undef SKIP
|
|
|
|
return visited;
|
|
}
|
|
|
|
void ObjectNode::resetUnvisited(const QSet<QString>& visited) {
|
|
const WriteScope scope(this, WriteOrigin::FileReset);
|
|
|
|
for (const auto& desc : schema().descriptors()) {
|
|
if (visited.contains(desc.key)) continue;
|
|
|
|
// Reset nodes recursively
|
|
if (desc.isNode) {
|
|
value(desc.key).value<Node*>()->resetToDefaults();
|
|
continue;
|
|
}
|
|
|
|
// Skip global options on overlays
|
|
if ((m_globalOnly || desc.globalOnly()) && fallbackNode()) continue;
|
|
|
|
setValue(
|
|
desc.key,
|
|
fallbackNode() ? fallbackNode()->value(desc.key)
|
|
: desc.defaultValue());
|
|
}
|
|
}
|
|
|
|
} // namespace ZShell::settings
|