6.7 KiB
6.7 KiB
src/shell/persist — project ext-state persistence, prune filesystem I/O, usage scan
Scope
The persist seam: project ext-state read/write (session / ext_state_io), the
prune path's filesystem half (prune_fs), and the extension-side instance-usage
scan (usage_scan). Internal helpers shared only within the persist TU family live
in persist_internal.h. The pure orphan computation is owned elsewhere
(core/reclaim), the pure usage wire elsewhere again (core/wire), and every
tracking decision by core/tracking's authority — this directory is the
REAPER/filesystem-facing half only, and it gathers rather than decides.
Invariants
- Prune is the single, exclusive file-deletion authority. No bank op, no
capture op, no Design View op deletes a file; if any path other than prune
deletes a bank file, reject it in review. The ONE carve-out — a shell removing
a file it wrote itself moments earlier that no index ever referenced, wherever
it sits — is stated in full at
prune_fs.cpp's header and nowhere else. - Dry-run first, always; no silent deletion. Prune reports before it deletes (orphan count, reclaimed size, and — for a small set — the files); actual deletion is a confirmed second step. No periodic/background sweep.
- Referenced-set is the union across ALL banks, pool included, further unioned
with every live instance's held paths — supplied by
tracking::pruneProtection, never assembled here — viaprune_reconcile::mergeReferenced. A file is an orphan iff no bank AND no live instance references it. - Safest platform deletion available. Trash-preferred, unlink fallback — Windows
routes through
SHFileOperationW(FOF_ALLOWUNDO, verified against SDK 10.0.26100); macOS/Linux fall back to unlink (no portable SWELL trash surface).prune_fsis the only module that calls this. - Manual, explicit trigger only — a bindable action + a
bank_panelbutton, never a silent background sweep. - Instance-usage fail-safe: a capture held by any live ReaSampler 9000 instance
can never be deleted by prune.
usage_scandecodes everyrsusage_*key, enumerates every ReaSampler 9000 FX instance (all tracks incl. master, normal + record/input chains, containers recursively, take FX), and folds via the puresample_usage::foldUsageRecords. Read-only at prune-scan time:usage_scanwrites no ext-state. - A ledger this build cannot read is degraded, never "empty".
ext_state_iokeeps theLedgerStatusalongside the ledger, andsaveToActiveProjectSKIPS theowned_fileswrite whiletracking::ledgerDegradedholds — replacing a blob we could not read would destroy the only record of every file created before it, silently turning them into permanently unreclaimable foreign files. The status is written only byloadFromProject, so it is sticky until the project is reloaded: captures made during such a session are recorded in memory but not persisted, and degrade to foreign (untouchable), which is the safe direction. PruneReportcarriesblockedByTracking+ledgerUnreadable/ledgerFutureVersion/unreadableUsageKeys; dry-run, orphan-set, and reclaim each independently abort (delete nothing) on a block.BANK_PRUNE_FOLDER(inshell/actions) halts on the flag and prints whichever blockers fired, with channel-correct recovery lines — a corrupt blob may be cleared, a newer build's must not be.
Modules
shell/persist(session/ext_state_io/prune_fs) — the persist seam, split by responsibility (Q-W5; the formerpersist.cppgod-TU and itspersist.hcompatibility umbrella are both retired — callers includeshell/persist/session.h/ext_state_io.hdirectly).sessionowns theReaSamplerSessionlifecycle: the poll identity-transition detection (load / Save-As / forked sibling / recycled pointer) and theprojectconfig-driven deferred undo/redo reload.ext_state_ioowns project ext state (SetProjExtState/GetProjExtState, namespace"reasampler") ↔BankBookJSON,ViewModeModelJSON,TailSettingJSON, the tracking ledger JSON, the writing-version stamp, GUID minting, and bank-folder relocation.sessionadditionally ownsrecordCreated— the one writer of a birth record, called at the same point theSampleis added, deriving lineage from thatSample's own provenance.prune_fshosts the prune dry-run / full-set orphan queries (gatheringreferencedPaths()plustracking::pruneProtection's two inputs for theprune_reconcilepure core) — and, beside them,tiedUsageFor, the resample's replace-vs-add input, deliberately co-located so "both answers come out of oneTrackingState" is structural rather than a rule two files must remember. It is also the single file-deletion authority over user files in the bank folder (deleteOrphanFileviaSHFileOperationW); nothing else in the system deletes bank-folder bytes. Dry-run / orphan-set / reclaim each independently abort (delete nothing) when the authority reports a block.usage_scan— extension-side prune-scan shell: enumerates everyrsusage_*ext-state key, decodes eachsample_usagewire record, enumerates every ReaSampler 9000 FX instance across all tracks + master / normal + record chains / containers (recursive) / take FX, and returns the puresample_usage::foldUsageRecordsresult verbatim. One of the two inputstracking::pruneProtectionreads; it decides nothing itself. Read-only: writes no ext-state.persist_internal.h— internal-only shared helpers for the persist TU family (session/ext_state_io/prune_fs); included only by those three TUs, never a public seam (mirror of the panel'spanel_state.h/ the editor'seditor_internal.hprecedent). Holds the former anonymous-namespace helpers more than one split TU needs (active-project +.rpppath lookup, project-dir derivation, growingGetProjExtStateread, project-GUID minting, bank-folder relocation) — all definitions live inext_state_io.cpp. REAPER-free header: the project handle crosses this seam as the same opaquevoid*the publicsessionheader already uses.
Gotchas
- The pure orphan computation (
prune_reconcile,(owned ∩ present) − referenced) is documented undercore/reclaim, not here — do not duplicate its spec in this file. - The pure usage wire (
sample_usage:UsageRecord,planUsagePublish,foldUsageRecords/usageHeldPaths,identityMatches) is documented undercore/wire, and the ledger + the two consumer answers undercore/tracking— neither belongs in this file. persist_internal.his an internal seam, not a public header — do not include it outsidesession.cpp/ext_state_io.cpp/prune_fs.cpp.