113 lines
4.1 KiB
C++
113 lines
4.1 KiB
C++
#include "core/model/owned_manifest.h"
|
|
|
|
#include <cctype>
|
|
|
|
#include "core/json/json.h"
|
|
|
|
// owned_manifest implementation.
|
|
//
|
|
// JSON rides on the shared core/json lexical layer (Q-W1, mirror of bank_model /
|
|
// bank_book / tail_control). The shape is a single object with one string array:
|
|
//
|
|
// {"owned":["reasampler_bank/a.wav","reasampler_bank/b.wav"]}
|
|
//
|
|
// so a compact writer + a focused string-array domain parse is all it needs.
|
|
|
|
namespace reasampler::model {
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// path invariant (mirror of bank_model's isAbsolutePath)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
namespace {
|
|
|
|
// Any leading '/' or '\' (POSIX root / UNC), or a leading <alpha>: (Windows drive,
|
|
// incl. drive-relative "C:foo") is absolute. Same rejection bank_model applies to
|
|
// Sample.relativePath — the manifest holds the SAME kind of path, so the invariant
|
|
// must match exactly (a path the index accepts must be recordable, and vice versa).
|
|
bool isAbsolutePath(const std::string& p) {
|
|
if (p.empty()) return false;
|
|
if (p[0] == '/' || p[0] == '\\') return true;
|
|
if (p.size() >= 2 && std::isalpha(static_cast<unsigned char>(p[0])) && p[1] == ':')
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// mutation / query
|
|
// ---------------------------------------------------------------------------
|
|
|
|
ManifestAddResult OwnedFileManifest::add(const std::string& relativePath) {
|
|
if (relativePath.empty()) return ManifestAddResult::RejectedEmptyPath;
|
|
if (isAbsolutePath(relativePath)) return ManifestAddResult::RejectedAbsolutePath;
|
|
if (contains(relativePath)) return ManifestAddResult::AlreadyPresent;
|
|
paths_.push_back(relativePath);
|
|
return ManifestAddResult::Added;
|
|
}
|
|
|
|
bool OwnedFileManifest::contains(const std::string& relativePath) const {
|
|
for (const auto& p : paths_)
|
|
if (p == relativePath) return true;
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// JSON writer (shared core/json escape — byte-identical to the prior local one)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
std::string OwnedFileManifest::serialize() const {
|
|
std::string out = "{\"owned\":[";
|
|
for (std::size_t i = 0; i < paths_.size(); ++i) {
|
|
if (i) out += ',';
|
|
json::writeEscaped(out, paths_[i]);
|
|
}
|
|
out += "]}";
|
|
return out;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// JSON parser (string-array-only DOMAIN grammar over the shared core/json
|
|
// lexical layer). Tolerates unknown keys (forward-compat) and requires the
|
|
// "owned" value to be an array of strings.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
namespace {
|
|
|
|
bool parseManifest(json::Reader& r, OwnedFileManifest& out) {
|
|
if (!r.consume('{')) return false;
|
|
r.skipWs();
|
|
if (r.consume('}')) return true; // empty object -> empty manifest
|
|
for (;;) {
|
|
std::string key;
|
|
if (!r.parseKey(key)) return false;
|
|
if (key == "owned") {
|
|
std::vector<std::string> paths;
|
|
if (!r.parseStringArray(paths)) return false;
|
|
for (auto& p : paths) {
|
|
// Feed through add() so the persisted invariants (dedup, reject
|
|
// empty/absolute) are re-asserted on load — a hand-edited or corrupt
|
|
// blob cannot smuggle an absolute or duplicate path into the manifest.
|
|
out.add(p);
|
|
}
|
|
} else {
|
|
if (!r.skipValue()) return false; // forward-compat: tolerate unknown keys
|
|
}
|
|
r.skipWs();
|
|
if (r.consume(',')) continue;
|
|
if (r.consume('}')) return true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::optional<OwnedFileManifest> OwnedFileManifest::deserialize(const std::string& blob) {
|
|
OwnedFileManifest m;
|
|
json::Reader r(blob);
|
|
if (!parseManifest(r, m)) return std::nullopt;
|
|
return m;
|
|
}
|
|
|
|
} // namespace reasampler::model
|