feat(prune): R2 dry-run prune shell + persist wiring, report-only

Add ReaSamplerSession::pruneDryRun enumerating the resolved current bank
folder, feeding the R1 core, and returning a PruneReport (count/bytes/list).
New pure bankRelativeForName + buildPruneReport keep spelling and tally
testable. Register forever-stable BANK_PRUNE_FOLDER action, report-only. No deletion.
This commit is contained in:
2026-07-26 18:19:41 -04:00
parent 46176f3f04
commit c1473c42e1
10 changed files with 297 additions and 1 deletions
+64
View File
@@ -77,10 +77,13 @@
#include <filesystem>
#include <string>
#include <system_error>
#include <unordered_map>
#include <vector>
#include "app_version.h"
#include "capture_paths.h"
#include "prune_reconcile.h"
#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_EnumProjects
@@ -243,6 +246,67 @@ bool ReaSamplerSession::saveToActiveProject() {
namespace {
// The dry-run file-list display cap: the orphan COUNT and reclaimed SIZE are always
// exact (tallied over the full orphan set), but the enumerated file list handed to the
// console is clipped to this many entries so a project with thousands of orphans does
// not flood the report. PruneReport::truncated flags the clip. R3's confirm surface can
// choose its own presentation; this is purely the Wave-2 dry-run readout ceiling.
constexpr std::size_t kPruneListDisplayCap = 64;
} // namespace
PruneReport ReaSamplerSession::pruneDryRun() const {
PruneReport report;
std::string rppPath;
void* proj = readActiveProject(rppPath);
if (!proj || rppPath.empty()) return report; // no active/saved project -> nothing
// Resolve the CURRENT bank folder the same way the index does (M4): project dir of
// the live .rpp + the fixed bank subfolder. Never a stored absolute path, so a
// Save-As relocation is followed automatically. resolveBankFile is the shared M4
// arithmetic; feeding it the bank subfolder as the "relative path" yields the folder.
const std::string projectDir = projectDirOf(rppPath);
const std::string bankDir = resolveBankFile(projectDir, kBankSubfolder);
if (bankDir.empty()) return report; // unresolvable (no project dir) -> nothing
std::error_code ec;
if (!fs::exists(bankDir, ec) || !fs::is_directory(bankDir, ec)) {
return report; // no bank folder captured yet -> nothing to reclaim
}
// Enumerate the folder into project-relative index-spelled paths, spelled the SAME
// way the capture path spelled them (bankRelativeForName == deriveBankPaths's
// convention) so the pure core's exact-string match lines up with referencedPaths()
// and the manifest. Non-recursive: the bank folder is flat (capture writes files
// directly here); skip any subdirectory. Size is stat'd here and cached by relative
// path so the report's byte tally reuses the same on-disk read.
std::vector<std::string> present;
std::unordered_map<std::string, std::uint64_t> sizeByRel;
for (const auto& entry : fs::directory_iterator(bankDir, ec)) {
if (ec) break;
std::error_code fec;
if (!entry.is_regular_file(fec)) continue; // skip subdirs / specials
const std::string name = entry.path().filename().string();
const std::string rel = bankRelativeForName(name);
if (rel.empty()) continue;
present.push_back(rel);
const std::uintmax_t sz = entry.file_size(fec);
sizeByRel[rel] = fec ? 0 : static_cast<std::uint64_t>(sz);
}
// The decision lives in the pure core — read-only inputs from the session's book and
// manifest (NO save, NO MarkProjectDirty, NO mutation). referencedPaths() unions
// across the whole book (pool included); owned().paths() is the manifest set. The
// count / byte-sum / display-truncation tally is the pure buildPruneReport, so this
// shell only enumerates, resolves, and stats — no report logic re-implemented here.
const std::vector<std::string> orphans =
pruneOrphans(present, book_.referencedPaths(), owned_.paths());
return buildPruneReport(orphans, sizeByRel, kPruneListDisplayCap);
}
namespace {
// Load the Design-View model from a project's view_state key, or return a fresh
// default. An absent/empty key (older project with no view state) yields a
// default-constructed model (Arrange + Design seeded, active = Arrange) — graceful,