rm and dedupe leave the iTunesDB out of sync when a delete fails mid-batch #2
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
wokoman/ironpod#2
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
ironpod rmandironpod dedupedelete audio files in a loop and write the iTunesDB once at the end. Both loops propagate the first error with?, which returns from the function beforedb.write()runs:copy::removedeletes the audio file only after the in-memory database has stopped referencing it (src/copy.rs:477-486) — deliberately, so a failed unlink leaves an orphan file rather than a database entry pointing at nothing. That ordering is correct per call. It does not hold across a batch that never reachesdb.write().Failure
Remove 10 matching tracks. Track 6 fails to unlink — a read-only file, a FAT32 hiccup, the volume going away mid-run.
The iPod then shows five tracks that skip or hang when selected, and there is no way to remove them from the device —
ironpod rmmatches the database entry,copy::removefinds no file to delete and (correctly) treatsNotFoundas success, so it repairs itself on the next successful run. But until then the device is in a state the user did not ask for and cannot see.The write direction has no equivalent problem:
copy::copy_allreports per-file failures through a callback and always reaches its write.The TUI does not have this bug
src/tui.rs:994-1004runs the same loop but breaks on the first error and writes anyway, so its on-disk database matches what is actually on disk. The two removal paths have diverged in crash-consistency behaviour, which is the underlying issue — see the architecture note below.Fix
Make the CLI loops match the TUI: stop at the first failure, still write, then report. The minimum change is
breakplus anOption<anyhow::Error>in bothrmanddedupe.Note that three call sites now hand-roll "loop
copy::remove, thendb.write()once" while the copy direction hascopy::copy_allto do exactly this. Acopy::remove_allwith the same callback shape would make the consistency guarantee a property of the module rather than something each caller has to remember. That refactor is deliberately not in scope here — this ticket is the correctness fix, and the interface question is being grilled separately.Verification
Currently untestable in CI: the removal path needs a real
Database, and there is no in-memory fixture. Options, in order of cost:IRONPOD_MOUNT+IRONPOD_ALLOW_WRITE=1that copies two markers, chmods one file to force the unlink to fail, runs the batch, and asserts the reopened database lists exactly what is on disk.Databasefixture (separate ticket) and test it properly.Superseded as a work item by #5, which fixes this by routing every removal path through a batch interface that owns the write-once guarantee.
Do not implement the minimal fix described above — it collides with #5. This issue stays open as the bug of record and #5 closes it.
Dependency chain: #3 (test fixture) → #4 (batch removal) → #5.