Skip to content

[Feature]: A transfer queue behind the upload panel — retry, pause/resume, and a conflict policy #24735

Open

Description

@rorar

Problem or use case

Short summary: an upload is a one-shot attempt. If it fails — a dropped connection is the usual way — the transfer is simply gone: no record of what was being sent, no way to retry it, and the only recovery is to find the files again and re-drag them. (S)FTP clients solved this thirty years ago with a queue: items wait, run, pause, resume, fail visibly, and can be retried without being re-selected.

What the code does today, per source file, is { status: 'imported' | 'skipped' | 'failed', reason } from a single attempt (src/main/ipc/filesystem-runtime-upload-staging.ts:40-90). Nothing retries; nothing remembers. The SSH path is explicit about where that leaves the user — when the session is gone it throws "Remote file upload is unavailable. Reconnect the SSH target and retry." (src/main/providers/ssh-filesystem-file-upload.ts:25). The retry it asks for is entirely manual, and the list of what was in flight no longer exists anywhere.

Two things make this the right moment to ask:

And one behaviour nobody can currently change: collisions are never overwritten and never asked about. The import path picks a non-colliding name through deconflictName and reports renamed (src/main/ipc/filesystem-import-ssh.ts:160-187), while the transport opens the remote file with 'wx' because callers always pass exclusive: true (src/main/ssh/sftp-upload.ts:37-39, 73-74). Re-uploading a corrected file therefore produces a second copy beside the stale one, and the only way to replace the original is to delete it by hand first.

Proposed solution

A queue as the model behind the panel from #16107 / #23849 — not a second surface.

  1. Items with states. queued → running → done, plus paused, failed and skipped. An item carries source, destination workspace and path, size, bytes moved, attempt count, and the failure reason when it has one. Items survive the failure that ended their transfer — that is the whole point — and ideally an app restart, since a dropped VPN and a closed laptop look the same to the queue.
  2. Retry. Per item and Retry all failed. For transport-level failures (connection lost, session replaced) retry automatically on reconnect with a bounded backoff, because the user did nothing wrong and re-dragging is pure ceremony. For policy failures (too large, permission denied, unsupported type) do not auto-retry — show the reason and let the user decide.
  3. Pause and resume, distinct from the cancel that feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107 already implements. Cancel means undo this transfer and roll back what it created; pause means stop moving bytes, keep the item. The SFTP layer already has the primitives for a real resume: an abort signal per transfer, and an append flag that opens the remote file with 'a' (sftp-upload.ts:123-128), so a resumed item can continue at an offset rather than start over — worth having for the 2 GB case, and acceptable to re-start smaller files in a first version.
  4. A conflict policy, including Overwrite All. When a destination name exists, offer Overwrite, Overwrite all, Skip, Skip all, and Keep both — with Keep both as the default so today's behaviour is preserved for anyone who does not care. The choice applies to the rest of the batch, which is the part that matters when 40 files land on 40 existing ones. At the transport level this is already one flag: exclusive: false opens with 'w' instead of 'wx'.
  5. Order and concurrency. A small number of parallel transfers, reorderable, so one large file does not hold up three small ones; and a visible queue position, which is the thing an FTP client gives you that a spinner never does.
  6. Status at a glance. A per-row state icon (queued, running, paused, failed, done), the failure reason inline on a failed row ([Bug]: Drop upload failures report a count, never the reason #21219), and an aggregate indicator that survives switching away — docked at the bottom of the file explorer when the explorer is where the transfer came from, which is where the eye already is after a drop.

Sequencing: this should land after #16107 and #23849 rather than compete with them. They build the panel and the per-transfer plumbing (progress events, cancel, rollback ledger); this turns the list they render into a queue that outlives a failure.

Alternatives or additional context


Written with Claude Code (Opus 5); file and line references are from main today.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions