pS-usage: captures held by live ReaSampler 9000 instances are un-prunable — instances publish usage_<guid> ext-state records (ComponentState v11), prune unions live holds into referenced
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
// sample_usage.cpp — see sample_usage.h. Pure: standard library only.
|
||||
|
||||
#include "sample_usage.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kMagic = "rsusage1";
|
||||
|
||||
// Append one length-prefixed field: <decimal-len> ':' <bytes>. The same wire idiom as
|
||||
// assignment_request / provenance — one grammar across every ext-state seam.
|
||||
void putField(std::string& out, const std::string& field) {
|
||||
out += std::to_string(field.size());
|
||||
out += ':';
|
||||
out += field;
|
||||
}
|
||||
|
||||
// Bounds-checked cursor over the encoded string (the assignment_request Cursor, trimmed
|
||||
// to the two field kinds this record needs). A short read latches ok_ false.
|
||||
class Cursor {
|
||||
public:
|
||||
explicit Cursor(const std::string& s) : s_(s) {}
|
||||
|
||||
bool ok() const { return ok_; }
|
||||
bool atEnd() const { return pos_ >= s_.size(); }
|
||||
|
||||
bool field(std::string& out) {
|
||||
if (!ok_) return false;
|
||||
const std::size_t colon = s_.find(':', pos_);
|
||||
if (colon == std::string::npos) return fail();
|
||||
if (colon == pos_) return fail(); // empty length token
|
||||
if (colon - pos_ > 20u) return fail(); // SIZE_MAX is 20 decimal digits
|
||||
std::size_t len = 0;
|
||||
for (std::size_t i = pos_; i < colon; ++i) {
|
||||
const char c = s_[i];
|
||||
if (c < '0' || c > '9') return fail();
|
||||
const std::size_t digit = static_cast<std::size_t>(c - '0');
|
||||
if (len > (std::numeric_limits<std::size_t>::max() - digit) / 10u)
|
||||
return fail();
|
||||
len = len * 10u + digit;
|
||||
}
|
||||
const std::size_t start = colon + 1;
|
||||
if (start > s_.size() || len > s_.size() - start) return fail();
|
||||
out.assign(s_, start, len);
|
||||
pos_ = start + len;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consumes an exact literal at the cursor (the magic tag). Fails if absent.
|
||||
bool literal(const char* lit) {
|
||||
if (!ok_) return false;
|
||||
std::size_t i = 0;
|
||||
for (; lit[i] != '\0'; ++i) {
|
||||
if (pos_ + i >= s_.size() || s_[pos_ + i] != lit[i]) return fail();
|
||||
}
|
||||
pos_ += i;
|
||||
return true;
|
||||
}
|
||||
|
||||
// A length-prefixed unsigned decimal (the hold count). Fails on empty, non-digit,
|
||||
// or a value past a sane ceiling (a record cannot hold more entries than bytes).
|
||||
bool fieldCount(std::size_t& out) {
|
||||
std::string f;
|
||||
if (!field(f)) return false;
|
||||
if (f.empty() || f.size() > 10u) return fail();
|
||||
std::size_t v = 0;
|
||||
for (const char c : f) {
|
||||
if (c < '0' || c > '9') return fail();
|
||||
v = v * 10u + static_cast<std::size_t>(c - '0');
|
||||
}
|
||||
out = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool fail() {
|
||||
ok_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& s_;
|
||||
std::size_t pos_ = 0;
|
||||
bool ok_ = true;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string encodeUsageRecord(const UsageRecord& rec) {
|
||||
std::string out = kMagic;
|
||||
putField(out, rec.trackGuid);
|
||||
putField(out, std::to_string(rec.holds.size()));
|
||||
for (const UsageHold& h : rec.holds) {
|
||||
putField(out, h.sampleId);
|
||||
putField(out, h.relativePath);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::optional<UsageRecord> decodeUsageRecord(const std::string& wire) {
|
||||
Cursor c(wire);
|
||||
if (!c.literal(kMagic)) return std::nullopt;
|
||||
UsageRecord rec;
|
||||
if (!c.field(rec.trackGuid)) return std::nullopt;
|
||||
std::size_t count = 0;
|
||||
if (!c.fieldCount(count)) return std::nullopt;
|
||||
// Each hold needs at least 4 wire bytes ("0:0:"), so a count past wire.size()/4 is
|
||||
// provably bogus — reject before looping rather than iterating a crafted huge count.
|
||||
if (count > wire.size() / 4u + 1u) return std::nullopt;
|
||||
rec.holds.reserve(count);
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
UsageHold h;
|
||||
if (!c.field(h.sampleId)) return std::nullopt;
|
||||
if (!c.field(h.relativePath)) return std::nullopt;
|
||||
rec.holds.push_back(std::move(h));
|
||||
}
|
||||
if (!c.ok() || !c.atEnd()) return std::nullopt; // trailing garbage -> reject whole
|
||||
return rec;
|
||||
}
|
||||
|
||||
UsagePublishPlan planUsagePublish(const std::optional<std::string>& existing,
|
||||
const std::string& lastPublishedThisLifetime,
|
||||
const UsageRecord& mine) {
|
||||
UsagePublishPlan plan;
|
||||
plan.wire = encodeUsageRecord(mine);
|
||||
|
||||
if (!existing || existing->empty()) {
|
||||
// Fresh key — write mine.
|
||||
} else if (!lastPublishedThisLifetime.empty() &&
|
||||
*existing == lastPublishedThisLifetime) {
|
||||
// The key holds exactly what THIS instance wrote this lifetime: the normal
|
||||
// single-owner path. Clean replace (released holds genuinely drop).
|
||||
if (plan.wire == lastPublishedThisLifetime) plan.skipWrite = true;
|
||||
} else {
|
||||
const std::optional<UsageRecord> theirs = decodeUsageRecord(*existing);
|
||||
if (!theirs) {
|
||||
// Undecodable existing value — overwrite with mine (it protects nothing).
|
||||
} else if (theirs->trackGuid == mine.trackGuid) {
|
||||
// Foreign value from MY OWN track: my own persisted record from the last
|
||||
// session, or a same-track copy-sibling. Either way no hold in it may be
|
||||
// dropped by me — union, existing-first, de-duped. Over-protects (fail-safe)
|
||||
// until the next clean replace.
|
||||
UsageRecord merged;
|
||||
merged.trackGuid = mine.trackGuid;
|
||||
merged.holds = theirs->holds;
|
||||
for (const UsageHold& h : mine.holds) {
|
||||
bool dup = false;
|
||||
for (const UsageHold& e : merged.holds) {
|
||||
if (e == h) { dup = true; break; }
|
||||
}
|
||||
if (!dup) merged.holds.push_back(h);
|
||||
}
|
||||
plan.wire = encodeUsageRecord(merged);
|
||||
} else {
|
||||
// Foreign value from ANOTHER track: this instance is a cross-track copy (or
|
||||
// was moved). Take a fresh identity; never overwrite the other's record.
|
||||
plan.remint = true;
|
||||
}
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
std::vector<std::string> usageHeldPaths(
|
||||
const std::vector<UsageRecord>& records,
|
||||
const std::unordered_set<std::string>& liveTrackGuids,
|
||||
bool anyInstanceLive) {
|
||||
std::vector<std::string> out;
|
||||
std::unordered_set<std::string> seen;
|
||||
for (const UsageRecord& rec : records) {
|
||||
const bool live = rec.trackGuid.empty()
|
||||
? anyInstanceLive
|
||||
: (liveTrackGuids.count(rec.trackGuid) != 0);
|
||||
if (!live) continue;
|
||||
for (const UsageHold& h : rec.holds) {
|
||||
if (h.relativePath.empty()) continue;
|
||||
if (seen.insert(h.relativePath).second) out.push_back(h.relativePath);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user