import: a .rsbank lands as a new bank, whole or not at all

Four collisions answered explicitly: ids reminted, names never overwritten,
content deduped before the write, bank name auto-suffixed. Degraded ledger
refuses before the picker.
This commit is contained in:
2026-08-02 13:21:48 -04:00
parent 33ea95078d
commit a927dad2f4
26 changed files with 1689 additions and 24 deletions
+16 -6
View File
@@ -6,11 +6,12 @@ The filesystem and dialog acts behind bank-package export/import: streaming pack
file I/O plus the file-status and exclusive-create acts (`package_io`), the
UTF-8 path conversion every one of them goes through (`package_path`), the landed-file
journal and its rollback delete (`package_rollback`), and the two file pickers
(`package_pickers`). This seam is bytes-only — the package format (magic, manifest,
entry layout) is `core/package`'s business, and the export/import verbs that
orchestrate both do not live here yet. No REAPER project state is touched in this
directory: no ext-state read or write, no undo block, no generation bump — those
belong to the verbs.
(`package_pickers`). Those are bytes-only — the package format (magic, manifest, entry
layout) is `core/package`'s business. Beside them sits the import verb, split so its
decisions stay testable: `import_landing` (REAPER-free) decides and writes,
`import_bank` owns the only REAPER project state this directory touches (the
ext-state persist, the undo block, the generation bump). The export verb does not
live here yet.
## Invariants
@@ -65,7 +66,14 @@ belong to the verbs.
landed files with no index entry and a journal that now refuses to roll them
back — after which `rollback()` refuses and `writeLandedFile` refuses. (Destroying
an armed journal without calling either does NOT roll it back — see
`LandedFileJournal`'s own doc comment.)
`LandedFileJournal`'s own doc comment.) `import_bank` honours it: it calls
`markIndexCommitted()` only after `persistBankOp` has returned.
- **Integrity is proven before the first byte lands, not undone after.**
`landPackage` hashes every declared payload against the manifest and only then
creates the bank folder, so a damaged package costs no rollback at all and cannot
leave debris behind a rollback that itself failed. The second read of each payload
is deliberate on a once-per-gesture path — do not fold it into one
hash-and-write pass.
- **Both pickers ride `GetUserFileName`** — mode 1 for import, mode 0 for export.
There is no platform split and no fallback: `main.cpp` defines `REAPERAPI_IMPLEMENT`
without `REAPERAPI_MINIMAL` and aborts the extension load if any single name fails
@@ -77,6 +85,8 @@ belong to the verbs.
- `package_io` — every filesystem act the verbs need: `PayloadBuffer` (move-only payload + the `alive()` seam counter), `PackageFileWriter` (append-only temp+atomic-rename writer), `PackageFileReader` (seek-and-read one range per call, range-checked against the real file size), `readFilePayload` (one source file as one entry's payload), `fileStatus` (Present/Absent/Unreadable — export's refusal message must distinguish the last two, and an empty payload cannot), `writeFileExclusive` (exclusive create + write, self-cleaning on a partial write), and `listFolderFileNames` (bare UTF-8 names, sorted, non-recursive, non-throwing). REAPER-free; tested without a DAW.
- `package_rollback``LandedFileJournal`: `writeLandedFile` (exclusive-create land, path resolved absolute, recorded on success only), `markIndexCommitted` (disarms the journal), and `rollback` (deletes exactly the recorded set, hard unlink, tolerating a vanished file; refuses once disarmed). REAPER-free; tested without a DAW.
- `package_pickers``pickPackageForImport` and `pickPackageSavePath`, both `GetUserFileName`. Compile-only until the verbs land; nothing here can be exercised in a unit test.
- `import_landing` — the import's two halves that decide anything: `landPackage` (decode, plan, verify EVERY payload's digest, then land through the journal) and `applyImportedBank` (the new bank's entries plus a birth record per landed file, in one straight-line block). REAPER-free deliberately — all-or-nothing, integrity and birth-record behaviour are assertable without a DAW.
- `import_bank` — the promptless import verb over a live `ReaSamplerSession`: the project directory, the minted bank id, the `recordCreated` writer, and the one undo-batched persist. REAPER-facing, so it compiles into the extension module rather than into a library with a test target.
## Gotchas