Skip to content

Receiver rejects existing destination files before applying conflict policy #39

Description

@vsamarth

Summary

When a sender offers files whose transfer paths already exist in the receiver output directory, the receiver currently errors before the offer can be accepted. This happens even when the receiver is configured with ConflictPolicy::Rename, so the configured conflict policy is effectively ignored for normal destination conflicts.

Related: #27

Severity

Medium

Current behavior

If the receiver output directory already contains a file with the same relative path as an incoming transfer item, the receiver fails while building the offer:

  1. The receiver receives the sender manifest.
  2. Core calls build_expected_files(...) before the user decision phase.
  3. build_expected_files(...) resolves each destination path under the receiver output directory.
  4. It calls ensure_destination_available(...).
  5. ensure_destination_available(...) returns TransferPathError::DestinationExists as soon as the destination exists.
  6. The receiver sends a decline/error back to the sender and reports a file conflict.

The user sees a transfer failure instead of a renamed output path such as report (1).txt.

Expected behavior

The receiver's configured conflict policy should control this case:

  • Reject: fail before transfer and report a clear file-conflict error.
  • Rename: resolve the destination to the next available filename and continue.
  • Overwrite: remain unsupported for now if we do not want overwrite semantics yet, but it should be rejected explicitly at configuration time as it is today.

For the app's current default behavior, existing receiver files should not cause an error if the configured policy is Rename.

Root cause from investigation

The app and CLI expose ConflictPolicy, but it does not reach the core receiver planning path.

Relevant code:

  • crates/app/src/receiver/actor.rs: spawn_listener_task(...) accepts conflict_policy, rejects Overwrite, then drops the value.
  • crates/app/src/receiver/session.rs: creates CoreReceiverRequest with only device_name, device_type, and out_dir.
  • crates/core/src/transfer/receiver.rs: ReceiverRequest has no conflict_policy field.
  • crates/core/src/transfer/receiver.rs: build_expected_files(...) calls ensure_destination_available(...) directly and does not call ConflictPolicy::resolve(...).
  • crates/core/src/transfer/receiver.rs: TransferRecord::new(...) hard-codes ConflictPolicy::Rename, but that happens after destination validation, so it is too late to affect conflicts.
  • crates/core/src/fs_plan/conflict.rs: ConflictPolicy::resolve(...) already has the intended rename behavior, but receiver planning does not use it.

Important nuance

Existing destinations are currently allowed only for files already exported by a resume record. That path is covered by resumed_export_allows_destinations_for_already_exported_files and should remain valid.

So the desired fix should preserve resume behavior while applying conflict policy to ordinary destination conflicts.

Suggested fix shape

  • Add conflict_policy to CoreReceiverRequest.
  • Pass ReceiverConfig.conflict_policy from app runtime/listener/session into core.
  • Change build_expected_files(...) to take the policy.
  • Keep the existing resume-record exception for already exported files.
  • For non-resume conflicts, use ConflictPolicy::resolve(...) or equivalent shared destination-planning logic.
  • Persist the actual policy and resolved destinations in TransferRecord as appropriate.
  • Add focused tests for:
    • Rename resolves file.txt to file (1).txt when file.txt already exists.
    • Reject still fails with a file-conflict error.
    • Resume records can still reuse already-exported destinations.
    • App-level receiver config actually reaches core planning.

Verification idea

A regression test can construct a manifest containing report.txt, create report.txt in the receiver output directory, configure the receiver with ConflictPolicy::Rename, and assert that expected files resolve to report (1).txt rather than returning DestinationExists.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingseverity: mediumMedium severity bug; incorrect behavior or confusing workflow impact

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions