Files
reasampler/src/shell/persist/CLAUDE.md
T

6.7 KiB
Raw Blame History

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 — via prune_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_fs is the only module that calls this.
  • Manual, explicit trigger only — a bindable action + a bank_panel button, 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_scan decodes every rsusage_* key, enumerates every ReaSampler 9000 FX instance (all tracks incl. master, normal + record/input chains, containers recursively, take FX), and folds via the pure sample_usage::foldUsageRecords. Read-only at prune-scan time: usage_scan writes no ext-state.
  • A ledger this build cannot read is degraded, never "empty". ext_state_io keeps the LedgerStatus alongside the ledger, and saveToActiveProject SKIPS the owned_files write while tracking::ledgerDegraded holds — 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 by loadFromProject, 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.
  • PruneReport carries blockedByTracking + ledgerUnreadable / ledgerFutureVersion / unreadableUsageKeys; dry-run, orphan-set, and reclaim each independently abort (delete nothing) on a block. BANK_PRUNE_FOLDER (in shell/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 former persist.cpp god-TU and its persist.h compatibility umbrella are both retired — callers include shell/persist/session.h / ext_state_io.h directly). session owns the ReaSamplerSession lifecycle: the poll identity-transition detection (load / Save-As / forked sibling / recycled pointer) and the projectconfig-driven deferred undo/redo reload. ext_state_io owns project ext state (SetProjExtState/GetProjExtState, namespace "reasampler") ↔ BankBook JSON, ViewModeModel JSON, TailSetting JSON, the tracking ledger JSON, the writing-version stamp, GUID minting, and bank-folder relocation. session additionally owns recordCreatedthe one writer of a birth record, called at the same point the Sample is added, deriving lineage from that Sample's own provenance. prune_fs hosts the prune dry-run / full-set orphan queries (gathering referencedPaths() plus tracking::pruneProtection's two inputs for the prune_reconcile pure core) — and, beside them, tiedUsageFor, the resample's replace-vs-add input, deliberately co-located so "both answers come out of one TrackingState" 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 (deleteOrphanFile via SHFileOperationW); 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 every rsusage_* ext-state key, decodes each sample_usage wire record, enumerates every ReaSampler 9000 FX instance across all tracks + master / normal + record chains / containers (recursive) / take FX, and returns the pure sample_usage::foldUsageRecords result verbatim. One of the two inputs tracking::pruneProtection reads; 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's panel_state.h / the editor's editor_internal.h precedent). Holds the former anonymous-namespace helpers more than one split TU needs (active-project + .rpp path lookup, project-dir derivation, growing GetProjExtState read, project-GUID minting, bank-folder relocation) — all definitions live in ext_state_io.cpp. REAPER-free header: the project handle crosses this seam as the same opaque void* the public session header already uses.

Gotchas

  • The pure orphan computation (prune_reconcile, (owned ∩ present) referenced) is documented under core/reclaim, not here — do not duplicate its spec in this file.
  • The pure usage wire (sample_usage: UsageRecord, planUsagePublish, foldUsageRecords/usageHeldPaths, identityMatches) is documented under core/wire, and the ledger + the two consumer answers under core/tracking — neither belongs in this file.
  • persist_internal.h is an internal seam, not a public header — do not include it outside session.cpp / ext_state_io.cpp / prune_fs.cpp.