Number a colliding filename instead of failing the transfer - #35
Open
op-q wants to merge 2 commits into
Open
Conversation
Receiving a file whose name was already taken killed the whole transfer. The receiver refused before a byte moved, returned an error, and dropped the socket; the relay read that as a disconnect and told the sender "receiver disconnected", which blames the wrong side and gives no hint that a filename was the problem. The session was consumed either way, so the code could not simply be reused. A single file whose name is taken is now saved beside the original with a number added: `report.pdf` arriving twice leaves `report.pdf` and `report-1.pdf`. The receiver's existing file is still never replaced without `--force`, which is the invariant that mattered; refusing the transfer was never required to uphold it. The name is claimed with `create_new` rather than by testing `exists()` and then creating. That is what makes the numbering correct under concurrency: the old check left a window in which two receives could agree on the same free name and one would silently overwrite the other. Archive extraction is unchanged. A colliding entry inside a tar is still skipped and reported while the rest continues, because renaming individual files inside a tree the sender laid out would produce a directory that matches neither what was sent nor what was already there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The behaviour change was documented in the README, but the reasoning and the loose ends lived only in a pull request description — which is where the relay teardown defect sat for a month before it was rediscovered. Adds a decision entry for the collision policy, because the split between it and archive extraction reads as an inconsistency and will invite someone to "fix" one side into matching the other. Adds three items to the unscheduled list: lowercase session codes being rejected, "receiver disconnected" being the sender's message for every receiver-side failure, and the published binary reporting 0.1.0 while the tag says v0.1.1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Reported from a real transfer: receiving a file whose name was already taken killed the transfer on both sides.
Why it was worse than it looks
The receiver refused before a byte moved, returned an error, and dropped the socket. The relay read that as a disconnect and told the sender
receiver disconnected— which blames the wrong side and gives no hint that a filename was the cause. The session was consumed either way, so the code could not be reused; both people had to start over.Change
A single file whose name is taken is saved beside the original with a number added, so
report.pdfarriving twice leavesreport.pdfandreport-1.pdf. The number goes before the extension so the file still opens with the right application.The receiver's existing file is still never replaced without
--force— that was the invariant worth protecting, and refusing the transfer was never required to uphold it.The name is claimed with
create_newrather than by testingexists()and then creating. That is what makes numbering correct under concurrency: the old check left a window where two receives could agree on the same free name and one would silently overwrite the other. Closing that was not the point of this change, but leaving it while adding a retry loop on top would have made it easier to hit.Archive extraction is unchanged. A colliding entry inside a tar is still skipped and reported while the rest continues. Renaming individual files inside a tree the sender laid out would produce a directory matching neither what was sent nor what was already there.
Verification
Reproduced the original scenario against the live relay:
The sender now completes instead of reporting a disconnect.
Tests: 62 passing, up from 56 — five unit tests covering the naming rule (extension placement, no extension, leading dot, walking past taken names, never replacing an existing file) and one integration test that sends the same file twice into one destination and asserts both copies exist.
scripts/check-secrets.sh,cargo fmt --check,cargo clippy --workspace --all-targets --all-features -D warningsall pass.Docs
README updated to describe the split behaviour (numbered for a single file, skipped for an archive entry). The receiver-confirmation plan is updated too: numbering makes the prompt-ordering constraint sharper, since a prompt placed after
open_targetwould now leave an emptyreport-1.pdfbehind on decline and consume that name.Not fixed here
Two other papercuts from the same session, both worth their own change:
drop recv 4607f9returnsinvalid session code;4607F9works. The relay stores codes uppercase andencode_codedoes not normalise case. A two-line client-side fix.receiver disconnectedis the sender's message for any receiver-side failure. This change removes the most common cause, but a receiver that fails for another reason still produces a misleading message. The receiver could send anerrorcontrol frame before closing so the sender can say what actually happened.🤖 Generated with Claude Code