tracking: one ledger, one authority — prune protection and replace-vs-add answered from the same records, fail-safe on unreadable state
This commit is contained in:
@@ -33,10 +33,11 @@
|
||||
|
||||
#include "shell/persist/persist_internal.h"
|
||||
#include "shell/persist/session.h"
|
||||
#include "shell/persist/usage_scan.h" // liveInstanceHeldPaths — instance holds join `referenced`
|
||||
#include "shell/persist/usage_scan.h" // scanInstanceUsage — one of the authority's two inputs
|
||||
|
||||
#include "core/capture/capture_paths.h" // resolveBankFile / bankRelativeForName / kBankSubfolder
|
||||
#include "core/reclaim/prune_reconcile.h" // the pure orphan decision + report tallies
|
||||
#include "core/capture/capture_paths.h" // resolveBankFile / bankRelativeForName / kBankSubfolder
|
||||
#include "core/reclaim/prune_reconcile.h" // the pure orphan decision + report tallies
|
||||
#include "core/tracking/tracking_authority.h" // the one protection answer
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
@@ -62,21 +63,22 @@ constexpr std::size_t kPruneListDisplayCap = 64;
|
||||
// active/saved project, no project dir, or no folder yet.
|
||||
// * orphans — the full orphan set, untruncated. The pure core decides.
|
||||
// * sizeByRel — per-orphan on-disk byte size (0 when it could not be stat'd).
|
||||
// * abortedUnreadableUsage — true iff a present rsusage_* record could not
|
||||
// be read/decoded: `orphans` is left EMPTY, the prune must
|
||||
// halt rather than proceed with degraded protection.
|
||||
// * blocked — true iff the tracking authority could not answer: `orphans`
|
||||
// is left EMPTY, the prune must halt rather than proceed with
|
||||
// degraded protection.
|
||||
struct PruneScan {
|
||||
std::string bankDirAbs;
|
||||
std::vector<std::string> orphans;
|
||||
std::unordered_map<std::string, std::uint64_t> sizeByRel;
|
||||
bool abortedUnreadableUsage = false;
|
||||
std::vector<std::string> offendingUsageKeys; // non-empty iff abortedUnreadableUsage
|
||||
bool blocked = false;
|
||||
bool ledgerUnreadable = false;
|
||||
std::vector<std::string> unreadableUsageKeys;
|
||||
};
|
||||
|
||||
// Non-throwing: readActiveProject + resolveBankFile are pure/string; every filesystem
|
||||
// call below uses an error_code form so no std::filesystem_error crosses REAPER's C ABI.
|
||||
PruneScan scanPruneOrphans(const BankBook& book,
|
||||
const model::OwnedFileManifest& owned) {
|
||||
PruneScan scanPruneOrphans(const BankBook& book, const tracking::OriginLedger& ledger,
|
||||
tracking::LedgerStatus ledgerStatus) {
|
||||
PruneScan scan;
|
||||
|
||||
std::string rppPath;
|
||||
@@ -97,7 +99,7 @@ PruneScan scanPruneOrphans(const BankBook& book,
|
||||
|
||||
// Enumerate into project-relative paths spelled the SAME way the capture
|
||||
// path spells them, so the pure core's exact-string match lines up with
|
||||
// referencedPaths() and the manifest. Non-recursive: the bank folder is
|
||||
// referencedPaths() and the ledger. Non-recursive: the bank folder is
|
||||
// flat. Manual iterator form (it.increment(ec)) keeps the loop
|
||||
// non-throwing on a mid-iteration failure.
|
||||
std::vector<std::string> present;
|
||||
@@ -115,28 +117,30 @@ PruneScan scanPruneOrphans(const BankBook& book,
|
||||
scan.sizeByRel[rel] = sz_ec ? 0 : static_cast<std::uint64_t>(sz);
|
||||
}
|
||||
|
||||
// The decision lives in the pure core — read-only inputs from the book and
|
||||
// manifest. referencedPaths() unions across the whole book; the referenced
|
||||
// set additionally unions every LIVE ReaSampler 9000 instance's held
|
||||
// captures (usage_scan + sample_usage decide liveness) — a capture any
|
||||
// live instance holds can never be an orphan, even if its bank entry was
|
||||
// deleted while the instance kept its ref. liveInstanceHeldPaths is
|
||||
// read-only; this shell only enumerates, resolves, and stats.
|
||||
// The decision lives in the pure core; the tracking authority supplies both of
|
||||
// its tracking-derived inputs so the prune and the resample can never disagree
|
||||
// about what is protected. referencedPaths() unions across the whole book; the
|
||||
// authority's heldPaths adds every live instance's captures on top — a capture
|
||||
// any live instance holds can never be an orphan, even if its bank entry was
|
||||
// deleted while the instance kept its ref. Read-only throughout: this shell
|
||||
// only enumerates, resolves, and stats.
|
||||
scan.bankDirAbs = bankDir;
|
||||
const UsageScanResult usage = liveInstanceHeldPaths(proj);
|
||||
if (usage.abortPrune) {
|
||||
// FAIL-SAFE ABORT: a present rsusage_* record could not be read/decoded,
|
||||
// so the protected set is unknowable. Compute NO orphans — every
|
||||
// downstream consumer then deletes nothing. The key names let the
|
||||
// action tell the user which keys to recover.
|
||||
scan.abortedUnreadableUsage = true;
|
||||
scan.offendingUsageKeys = usage.offendingKeys;
|
||||
const wire::UsageFoldResult usage = scanInstanceUsage(proj);
|
||||
const tracking::TrackingState state{ledgerStatus, ledger, usage};
|
||||
const tracking::ProtectionAnswer protection = tracking::pruneProtection(state);
|
||||
if (protection.blocked) {
|
||||
// FAIL-SAFE ABORT: the protected set is unknowable. Compute NO orphans —
|
||||
// every downstream consumer then deletes nothing. The blockers let the
|
||||
// action tell the user what to recover.
|
||||
scan.blocked = true;
|
||||
scan.ledgerUnreadable = protection.ledgerUnreadable;
|
||||
scan.unreadableUsageKeys = protection.unreadableUsageKeys;
|
||||
return scan;
|
||||
}
|
||||
scan.orphans = reclaim::pruneOrphans(
|
||||
present,
|
||||
reclaim::mergeReferenced(book.referencedPaths(), usage.heldPaths),
|
||||
owned.paths());
|
||||
reclaim::mergeReferenced(book.referencedPaths(), protection.heldPaths),
|
||||
protection.ownedPaths);
|
||||
return scan;
|
||||
}
|
||||
|
||||
@@ -199,19 +203,22 @@ bool deleteOrphanFile(const std::string& absPath, bool& outUsedTrash,
|
||||
} // namespace
|
||||
|
||||
reclaim::PruneReport ReaSamplerSession::pruneDryRun() const {
|
||||
const PruneScan scan = scanPruneOrphans(book_, owned_);
|
||||
const PruneScan scan = scanPruneOrphans(book_, tracking_, trackingStatus_);
|
||||
reclaim::PruneReport report =
|
||||
reclaim::buildPruneReport(scan.orphans, scan.sizeByRel, kPruneListDisplayCap);
|
||||
// Surface the unreadable-usage abort so the action halts with an explicit
|
||||
// message instead of reporting "no orphaned files" — the count IS zero,
|
||||
// but the user must know the prune refused to run.
|
||||
report.abortedUnreadableUsage = scan.abortedUnreadableUsage;
|
||||
report.offendingUsageKeys = scan.offendingUsageKeys;
|
||||
// Surface the block so the action halts with an explicit message instead of
|
||||
// reporting "no orphaned files" — the count IS zero, but the user must know
|
||||
// the prune refused to run.
|
||||
report.blockedByTracking = scan.blocked;
|
||||
report.ledgerUnreadable = scan.ledgerUnreadable;
|
||||
report.unreadableUsageKeys = scan.unreadableUsageKeys;
|
||||
return report;
|
||||
}
|
||||
|
||||
std::vector<std::string> ReaSamplerSession::pruneOrphanSet() const {
|
||||
return scanPruneOrphans(book_, owned_).orphans; // full set, untruncated
|
||||
// Full set, untruncated; empty on a block, so a caller that skipped the report
|
||||
// still confirms nothing.
|
||||
return scanPruneOrphans(book_, tracking_, trackingStatus_).orphans;
|
||||
}
|
||||
|
||||
reclaim::PruneDeletionResult ReaSamplerSession::pruneReclaim(
|
||||
@@ -222,10 +229,10 @@ reclaim::PruneDeletionResult ReaSamplerSession::pruneReclaim(
|
||||
// targets exactly `confirmed ∩ freshOrphans`, so a file that vanished or
|
||||
// became referenced between confirm and delete is skipped, and a newly-
|
||||
// appeared orphan not in `confirmed` is never swept. If this fresh scan
|
||||
// hits an unreadable usage record it aborts with an EMPTY orphan set, so
|
||||
// hits unreadable tracking state it aborts with an EMPTY orphan set, so
|
||||
// the plan below intersects to empty and nothing is deleted — the
|
||||
// fail-safe holds even in the confirm-to-delete window.
|
||||
const PruneScan scan = scanPruneOrphans(book_, owned_);
|
||||
const PruneScan scan = scanPruneOrphans(book_, tracking_, trackingStatus_);
|
||||
if (scan.bankDirAbs.empty()) return result; // no project / no folder -> nothing
|
||||
|
||||
const std::vector<std::string> plan =
|
||||
|
||||
Reference in New Issue
Block a user