Repository navigation
fix(export): preserve existing preset and carousel files - #258
Merged
Merged
Conversation
johnny4young
marked this pull request as ready for review
October 6, 2026 03:50
johnny4young
commented
Oct 6, 2026
johnny4young
left a comment
Owner
Author
There was a problem hiding this comment.
Review: preserve existing preset and carousel files
What it does: GUI multi-size and carousel exports now plan every image and sidecar name up front. Names are case-folded and NFC-normalized, and occupied or duplicate names get -2, -3, … suffixes. Each file is staged in a private mkdtemp sibling and published with renamex_np(RENAME_EXCL), so neither existing files nor files created after planning are ever replaced. A failed sidecar that was requested is now reported, and cancellation stops publication.
Verdict: ready. No blocking defects found.
What I checked
BatchOutputFiles.plan: it reserves the image and the sidecar together, sovitrine-x-2.pngnever pairs with someone else'svitrine-x-2.txt. Duplicates within the batch are handled through the sharedoccupiedset. Invalid names (/, NUL,.,..) are rejected.publish: themkdtemptemplate is built fromutf8CStringand decoded back correctly. Thedeferremoves only the private stage, never a published file. A race-created destination (including a symlink) makesRENAME_EXCLfail withEEXISTand is preserved, as the test checks.- Ownership:
writePresetnow takes an injected@Sendablewriter. Cancellation is checked before each image and sidecar commit. - The
.txtsidecar name matches the olddeletingPathExtension().appendingPathExtension("txt")derivation. - CI was green on
5fc63bf(all 5 required jobs).
Questions and nits (not blocking)
- 🟡 Non-APFS destinations:
renamex_np(RENAME_EXCL)can returnENOTSUPon some filesystems (certain SMB/AFP shares, FAT/exFAT USB sticks, depending on the macOS VFS support). If that happens, every file in the batch fails where a plain write used to work.DocumentationPackageWriteralready relies on the same call, so this matches existing practice. Still, a manual test against an exFAT stick or SMB share before release would be worthwhile. A fallback could beopen(O_CREAT|O_EXCL)on the destination plus a copy from the stage. - 🟡 If the app crashes between
mkdtempand cleanup, a hidden.vitrine-export-XXXXXXfolder is left in the user's chosen folder. That's harmless, but a sweep of stale.vitrine-export-*folders older than a day, run on the next export into the same folder, would tidy it up. - 🟡 Items skipped after cancellation count as
failed, so a cancelled batch reads "Some export files couldn't be written". No cancel button exists today, so this only happens on teardown. Worth revisiting if a Cancel button is added.
Generated by Claude Code
…ive rename Some network and FAT-family volumes answer renamex_np(RENAME_EXCL) with ENOTSUP, which failed every preset and carousel file there. Fall back to an O_CREAT|O_EXCL|O_NOFOLLOW create that still never replaces or follows an existing name and removes only its own file on a failed write. Read errno inside the call so the reported error is the rename's own.
…e-outputs # Conflicts: # CHANGELOG.md # Vitrine/Export/ExportManager+Batch.swift
…paths - Never close a fallback descriptor a second time after a failed close, since a concurrent export may already reuse that descriptor number. - Build the staging template with the same file-system representation used by the commit rename, so both name identical bytes for non-ASCII folders. - Create fallback files with 0o666 so the umask yields the same mode as the rename path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
What changed
Vitrine/Export/BatchOutputFiles.swift:planreserves image and sidecar names together, with case-folded, NFC keys and-2,-3, … suffixes.publishstages in a privatemkdtempsibling and commits withrenamex_np(RENAME_EXCL).ExportManager+Batch.swift:@Sendablewriter.Tests/BatchOutputFilesTests.swift,Tests/MultiSizeExportTests.swift. Docs:docs/ARCHITECTURE.md,CHANGELOG.md.Why
GUI batch exports used to overwrite existing
vitrine-<preset>.png,carousel-NN.pngand.txtfiles in the chosen folder, and a sidecar failure was silently ignored.Review follow-ups
6fe4ab1fix(export): keep batch publication working on volumes without exclusive rename.RENAME_EXCLwithENOTSUP/EOPNOTSUPP, which would have failed every file exported there.O_CREAT | O_EXCL | O_NOFOLLOWcreate. It still never replaces or follows an existing name, and on a failed write it removes only the file it created.errnois now captured inside the rename call, so the reported error is the rename's own.exclusiveCreateFallbackNeverReplacesOrFollowsAName.How it was tested
5fc63bf: complete CI passed (Linux static checks; Sequoia/Tahoe formatting, Debug and universal Release, coverage, performance, goldens; both UI and strict screenshot-tour lanes). All eight new regressions executed on each SDK.6fe4ab1:swiftc -parsepasses on the changed files.BatchOutputFiles.swiftwas also type-checked and executed on Linux with Swift 6.0.3 in Docker, with a Glibc shim and a stubrenamex_npthat forcesENOTSUP. That run checked:EEXISTand keeps the original;planproducesimage-2/-3with matching sidecars.Decisions taken
Known limitations and follow-ups
.vitrine-export-XXXXXXfolder can remain in the chosen folder. It is harmless. No automatic sweep was added, to avoid deleting anything in a user folder.failed. There is no Cancel button today, so this only happens on teardown.Scope
Independent main-based PR. No merge, release or deployment.