#include "objectnode.hpp" #include #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()->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()) { 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& 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(); m_quarantine->insert(key, value); } QSet ObjectNode::loadFromJson( const QJsonObject& json, QList& diagnostics) { const WriteScope scope(this, WriteOrigin::File); QSet 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(); 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& 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()->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