Skip to content

feat(files): stream uploads and cross-host copies (no 32 MB limit, folders on SFTP) - #24

Merged
Atreus-X merged 7 commits into
mainfrom
feat/streaming-upload-stacked
Sep 29, 2026
Merged

Atreus-X merged 7 commits into
mainfrom
feat/streaming-upload-stacked

Conversation

@Atreus-X

Copy link
Copy Markdown
Owner

Stacked on #23 (shares its streaming reader) — merge #23 first; this PR's diff then shrinks to its own commit.

What

Uploads (drag-and-drop from the desktop) and every copy between hosts used to hit two limits:

  • 32 MB per file on both no-wsh (SFTP) and wsh hosts — each file went as one RPC message.
  • No folders on SFTP hosts (copyViaServer was single-file only).

Now copies between different hosts, and any copy with an SFTP host on either side, go through one streaming copy in wavesrv (pkg/remote/fileshare/wshfs/streamcopy.go):

  • the source is streamed (SFTP read for no-wsh hosts, wsh stream for wsh hosts, local files too), written as a truncating write followed by 16 MB appends — no size limit;
  • folders are copied recursively on both kinds of host (symlinked folders not followed);
  • same semantics and error strings as before, so the UI's "Overwrite?" / "Sync" prompts keep working (existing file → overwrite required; existing folder → overwrite replaces, merge copies into it);
  • cross-host moves use it too (copy, then delete the source).

Same-host copies on a wsh host still run on that host; their 32 MB check is removed (it's a local io.Copy). copyViaServer is gone. wshfs.OpenStream is now the shared "open any file as a stream" helper and /wave/stream-zip uses it.

Both paths

no-wsh (SFTP) wsh
upload file > 32 MB ✅ streamed ✅ streamed
upload folder ✅ (new) ✅ (now via the same code)
cross-host move ✅ ✅
same-host copy ✅ relayed ✅ on the host, no size check

Testing

  • streamcopy_test.go against real local directories through the copy's hooks: 40 MB file byte-identical in exactly 3 writes, no write over the limit; empty file; overwrite-required then overwrite; folder needs recursive; nested folder copy; merge-required, merge keeps extra files and refreshes copied ones; overwrite replaces the folder; missing source.
  • All pkg/remote/... and pkg/web tests, go vet incl. cmd/wsh.
  • Not yet tried in the app against real SFTP and wsh hosts.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JhvhnSTH8EXbTG9wmtY7dd

Atreus-X and others added 7 commits September 28, 2026 13:27
- File browser rows support multi-selection: Ctrl/Cmd-click toggles,
  Shift-click selects a range, Ctrl/Cmd+A selects all, Esc / arrow keys /
  changing folder clear it. Selected rows are highlighted.
- Right-click inside a multi-selection: "Download N Items as Zip", plus copy
  file names / full paths / shell-quoted names. Single rows (files or
  folders) get "Download as Zip".
- New /wave/stream-zip endpoint builds the zip on the fly and streams it:
  folders recursively, each file streamed like /wave/stream-file (SFTP for
  no-wsh hosts, wsh streams for wsh hosts, local files too), so there's no
  32 MB transfer limit and nothing is staged on disk. Duplicate top-level
  names get " (2)"; unreadable files are listed in _download-errors.txt in
  the zip instead of failing the download; symlinked folders aren't
  followed. Electron downloads it through the normal download flow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JhvhnSTH8EXbTG9wmtY7dd
…lders on SFTP)

Copies between hosts - drag-and-drop uploads from the desktop included -
and any copy touching a no-wsh (SFTP) host now go through one streaming
copy in wavesrv (wshfs/streamcopy.go): the source is streamed (SFTP read
or wsh stream) and written as a truncating write followed by 16 MB
appends, and folders are copied recursively. Both kinds of host now get
files of any size and whole folders, with the same overwrite / merge
semantics and error strings the UI's "Overwrite?" / "Sync" prompts use.
Cross-host moves use it too (copy, then delete the source).

- replaces copyViaServer (SFTP only, single files, 32 MB)
- wsh same-host copies stay on the remote; their 32 MB check is dropped
  (it's a local io.Copy)
- wshfs.OpenStream is the shared "open any file as a stream" helper;
  /wave/stream-zip now uses it
- docs: connections.mdx

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JhvhnSTH8EXbTG9wmtY7dd
…pace)

- Shift+Up/Down/PageUp/PageDown extend the selection from the anchor
  (the focused row if nothing was selected), and can shrink it back
- Ctrl+Up/Down/PageUp/PageDown move the focus without changing the
  selection (a lone focused row becomes explicitly selected first)
- Ctrl+Space adds or removes the focused row
- fix: select-all was bound to Wave's "Cmd", which is Alt on Windows and
  Linux; it's now Ctrl+A there (Cmd+A on macOS), same for the keys above

The table publishes its row order (dirDisplayPaths) so keyboard ranges
follow the sorted display, like Shift-click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JhvhnSTH8EXbTG9wmtY7dd
Right-click a multi-selection -> "Delete N Items…", or press Delete with
several rows selected. Always asks first, listing the names (up to 8 +
"and N more"); folders in the selection are deleted with their contents.
Items that fail are listed afterwards; the selection clears and the
folder refreshes. Uses FileDeleteCommand, so no-wsh (SFTP), wsh and
local folders all work. Single-row delete is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JhvhnSTH8EXbTG9wmtY7dd
@Atreus-X
Atreus-X merged commit f0e49c9 into main Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant