Land the package filesystem shell: streaming atomic package_io, journaled rollback carve-out, asymmetric platform pickers
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# src/shell/package — package filesystem + dialog seam
|
||||
|
||||
## Scope
|
||||
|
||||
The filesystem and dialog acts behind bank-package export/import: streaming package
|
||||
file I/O (`package_io`), the landed-file journal and its rollback delete
|
||||
(`package_rollback`), and the platform 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.
|
||||
|
||||
## Invariants
|
||||
|
||||
- **Atomic write.** A package accumulates in a `.rsbanktmp` sibling in the
|
||||
destination directory and reaches the destination only through `commit()`'s
|
||||
rename (the mono-collapse temp+rename precedent). A failed, aborted, or abandoned
|
||||
write leaves the destination absent or holding its prior contents — never a
|
||||
partial `.rsbank`.
|
||||
- **Streaming, both ways — at most ONE entry's payload in memory.** Writes append
|
||||
one payload at a time; reads seek and materialize one range at a time. The claim
|
||||
is structural, not aspirational: every payload crosses this seam as a move-only
|
||||
`PayloadBuffer`, and `PayloadBuffer::alive()` is the seam counter the tests
|
||||
assert against. There is no read-whole-package or write-whole-package entry
|
||||
point; do not add one.
|
||||
- **The rollback delete is prune's ONE carve-out, cited not restated.** The
|
||||
citation and the discriminator live at `package_rollback.cpp`'s header. The
|
||||
journal makes the discriminator structural: only paths its own `writeLandedFile`
|
||||
successfully created are recorded, and `rollback()` consumes only the record — a
|
||||
path this import did not write cannot be handed to it.
|
||||
- **No overwrite of a bank-folder file, ever.** `writeLandedFile` refuses an
|
||||
existing destination outright; collision handling (auto-rename) is the import
|
||||
plan's job upstream. The package writer itself DOES replace an existing
|
||||
destination — the export save dialog's own overwrite confirm is the consent —
|
||||
and that asymmetry is deliberate.
|
||||
- **The two pickers are asymmetric, and the asymmetry is real.** Import rides
|
||||
REAPER's own `GetUserFileNameForRead` (both platforms); export goes native —
|
||||
Win32 `GetSaveFileNameW` / SWELL `BrowseForSaveFile` — because the always-present
|
||||
REAPER surface offers no save picker. Do not symmetrize; the newer
|
||||
`GetUserFileName(mode=0)` alternative and why it is not used are recorded in
|
||||
`package_pickers.cpp`'s header.
|
||||
|
||||
## Modules
|
||||
|
||||
- `package_io` — the streaming filesystem seam: `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), and `listFolderFileNames` (bare names, sorted, non-recursive, non-throwing). REAPER-free; tested without a DAW.
|
||||
- `package_rollback` — `LandedFileJournal`: `writeLandedFile` (temp+rename land, recorded on success only, refuses an existing destination and an empty payload) and `rollback` (deletes exactly the recorded set, hard unlink — nothing ever referenced these bytes — tolerating a vanished file). REAPER-free; tested without a DAW.
|
||||
- `package_pickers` — the two pickers in one platform TU (`#ifdef _WIN32` / `#else swell/swell.h`, the `draw_kit`/`prune_fs` split): `pickPackageForImport` (REAPER read picker) and `pickPackageSavePath` (native save dialog, UTF-8 in/out on Windows). Compile-only until the verbs land; nothing here can be exercised in a unit test.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- A crash mid-write strands the `.rsbanktmp` sibling. It is not a `.rsbank` (no
|
||||
picker filter matches it), and a later export to the same destination truncates
|
||||
it — but one stranded in the BANK folder by a mid-import crash is a foreign file
|
||||
to prune (not owned, so never an orphan) until removed by hand. `[verify — DAW]`
|
||||
whether the import verb should pre-clean stale `.rsbanktmp` names when it lands.
|
||||
- The picker `defext`/filter strings are spelled to the Win32 `lpstrDefExt`
|
||||
convention (no dot) but are `[verify — DAW]` on all three platforms — neither
|
||||
picker is exercised outside a live REAPER session.
|
||||
- `readRange(_, 0)` returns an empty buffer — indistinguishable from failure, by
|
||||
design (the one "nothing to work with" branch). A genuinely zero-length entry
|
||||
cannot round-trip through this seam; the format layer must not emit one.
|
||||
Reference in New Issue
Block a user