Repository navigation
Transfer 0.2.0
A revamp of the whole app. Transfer looks much the same, but what it does with your files is safer, logins are sturdier, copies are much faster, and a few behaviors change (listed below). The library upgrades itself on first launch.
Data safety
- A move removes an original only once it has checked that this move wrote a complete copy. A file of the same name, size, and time already at the destination no longer passes for the copy: it is asked about, and an original the user did not replace it with stays. Two saved servers that reach one disk, or a destination that is the originals' own folder reached another way, never copy an item onto itself and delete it. A folder holding a socket or FIFO, which no copy can write, keeps its original. Two items of one move with the same name (
p/VERSIONandq/VERSION) never count each other's copy. Between servers a move removes only what it checked: anything added to the original or changed after the check stays and is named, and so does an original whose Live file saved during the move. - Live files never lose edits the server lacks: a delete or move of a folder above one, Discard, Forget Synced Live Files, a conflict choice, and Remove Server all leave unsynced bytes alone unless you confirm. Delete discards only the edits its sheet named, and only after the server's delete succeeded. Keep Local and Keep Both wait until the working copy holds still, Keep Remote or Keep Both never overwrite a save made after the choice, and Keep Remote on a file removed from the server since keeps both copies. A save by someone else with the same size and second is no longer taken for your own upload, which left your edit marked synced and never uploaded.
- A server's names can no longer reach outside the folder you chose: a name that is not one path component is dropped from listings, and a link on the Mac is never followed or removed while downloading. A server link that met a local folder of the same name used to delete that folder.
- A change of case alone (
a.txttoA.txt) no longer replaces a different file on a case-sensitive server. A server withoutposix-renamekeeps the old file until the new one is in place. A name that appears after a folder was listed is never overwritten without asking, and a Replace removes only what you were asked about: a file that took the name while the question was up stays. - A folder copy into itself through a link on the server is refused; it used to copy until the disk filled.
- A temp that could not be removed stays recorded, and the next login removes it. On a server without
posix-rename, a file a replace set aside is recorded too, and the next login puts it back. - A download checks the file it opened against its listing, so a file that changed on the server since is read again, never placed cut short.
- Quit asks when Transfer cannot tell within 3 s whether Live edits are unsynced (it used to quit without asking), and names transfers that have not finished, closed windows' included; its question comes to the front.
- Stars, Live files, and temps whose names are not UTF-8 keep their exact bytes.
Security
- Downloads are quarantined as a browser's are, so Gatekeeper checks an app or script before it first runs, and never take setuid, setgid, or sticky bits from the server.
- A saved password or passphrase answers only that server's own prompt, never a
ProxyJumphost's. A saved key passphrase is stored apart from the server's password, so it is never sent to the server as a password. - A channel whose login has gone fails at once instead of logging in on its own with the default port and keys.
- A drop honors remote paths only from a drag that began in Transfer, so another app cannot have a drop rename files on your server.
- An
sftp://link with a control character in its path is refused, and Copy Remote URL brackets an IPv6 host. - Open in Terminal refuses a folder name holding a control character, which would have run the rest of the line as a command.
- A malformed, silent, or non-reading server fails its channel with a clear error instead of hanging it or filling memory.
- The installer installs only an app signed with Transfer's Developer ID and accepted by Gatekeeper as notarized, and follows redirects only over HTTPS.
Logins and reliability
- ssh checks host keys itself, so keys known through a global known-hosts file,
@cert-authority, orKnownHostsCommandlog in without a question, a known host costs one connection instead of two, and a@revokedkey is refused. Always Trust honorsHashKnownHosts. - Two windows or a retry logging in to one server share one login instead of asking twice and leaving a stray master behind. Cancel on any login question stops the login at once, with no error, and a window that moves to another server takes back that login's questions. Always Trust fails with a message when
ssh -Gcannot read the configuration, instead of trusting for one login only. A login no longer runs a remoteperformance-versioncommand. ControlPersist,ForkAfterAuthentication,RemoteCommand,RequestTTY,LocalForward, andPermitLocalCommandin~/.ssh/configno longer break channels, hold your forwarded ports, or leave a master Transfer cannot stop.- A server whose shell prints text before SFTP starts fails with a clear error within 15 s, and one that stops answering for 60 s fails its requests, which then retry.
- Every
sftp://link in a burst gets its own window; one could be lost before. - The column view no longer crashes when a drag crosses the area beyond its last column.
- A paste or folder copy goes on past an item that fails and names every failure at the end.
- A file whose name nearly fills the 255-byte limit copies both ways, and a Live file with such a name still gets its
(server)copy. - Copy Remote URL writes a name that is not UTF-8 byte for byte, and such a link opens that file. A server's error is no longer labeled twice ("Permission denied: Permission denied").
- A sheet never replaces another: one that arrives meanwhile waits its turn. A sheet about a server closes when the window leaves it, and Upload…, Download Copy…, and pastes act on the server they were started on or do nothing and say so.
- The rename bar's field takes the focus with Full Keyboard Access on, and emptying the search field keeps its focus, so Return no longer starts a rename.
- An unreadable
config.jsonis kept asconfig.json.bakbefore the defaults take over.
Performance
Measured at 20 ms round trip against 0.1.7:
- 2,000 small files: download 151 → 2,221 files/s, upload 44 → 1,616, copy on the server 60 → 1,488. Small files share a data channel, sixteen at a time, and a file's requests go out together.
- One 256 MB file: download 83 → 306 MB/s, upload 83 → 292 MB/s. A file of 8 MB or more uses up to four data channels.
- Listing 10,000 names: 670 → 224 ms.
- Column view with 10,000 items: Select All took one to ten minutes and takes about 12 ms; opening the column sorted by date, 3.3 s → 0.1 s.
- The inspector fetches only the first 64 KB of a text file it previews, and progress reaches the window ten times a second instead of once per 64 KB.
Behavior changes
- A name collision with no window left to ask fails its operation instead of skipping the file.
- Dragging between folders on one server moves; Option-drag copies (it used to move). A drag from another server's window copies (it used to be refused).
- Right-clicking an empty area acts on that folder: the list and icon views clear the selection, and the column view makes that column's folder the location.
- Command-Down and Space act once when held.
- A transfer's shelf row offers Stop and Restart, since a stopped transfer starts its current file over; a Live file's row keeps Pause and Resume.
- Duplicate works on folders and links, not only files, and a folder keeps its whole name:
v1.2 copy, andv1.2 2for Keep Both. - Keep Both for a Live conflict names the Mac's copy
notes (from this Mac).txt, so it keeps its extension. - A move that kept originals reads Kept on the shelf, in grey, with the reason. Later on a Live conflict keeps its sheet away until you click its sidebar row.
- Opening a link follows the whole chain, as the server resolves it, not one hop.
- Errors stay in a bar at the top of the window until dismissed; a successful listing used to clear them at once.
- A paste or move between servers of a folder holding two names this Mac's disk cannot tell apart (
READMEandreadme, orStraßeandSTRASSE) keeps that item and says why; such a copy is not staged for the Finder either. - Go to Remote Folder understands
~(the server's start folder),~/path, relative paths, and... - The list view keeps Name as its first column.
- Transfer refuses to open a library written by a newer version, says why, and quits.
- Only one copy of Transfer opens a library at a time; a second copy says so and quits. (Two copies swept each other's login files and temps and could end each other's connections.)
- The installer stops, changing nothing, on a Mac where Gatekeeper's assessments are turned off.
The library
transfer.sqlite now carries a schema version and upgrades itself on first launch, one step at a time: version 1 is the 0.1.0–0.1.7 schema; version 2 drops the unused recents table; version 3 changes no row and marks that paths are kept as the server's exact bytes, as text when they are UTF-8 (as before) and as bytes only when not. The file switches to WAL mode, so transfer.sqlite-wal and transfer.sqlite-shm appear beside it. Transfer 0.1.7 can still open an upgraded library, and still unstars and forgets what it finds there. The preview cache is keyed per server, so existing previews are fetched again once, and clipboard staging moves into one Staging folder, emptied at launch.
For maintainers
docs/SPEC.mdreplacesPLAN.md;HANDOFF.mdandAGENTS.mddescribe the code as it is.TRANSFER_LIBRARYgives a development build or a test its own library and caches.Scripts/local-sshd.sh [port]prints its exports once sshd listens and removes its keys when stopped;TRANSFER_REQUIRE_SERVER=1makes a missing test server fail the server suites instead of skipping them.Scripts/package-app.shprints the app's path and builds inSCRATCHwhen given.Scripts/release.shpublishes the version's section of this file as the release notes and in Sparkle's feed, refuses a version without one, and undoes a failed release.
Install or update with one command:
curl -fsSL https://raw.githubusercontent.com/shreeve/transfer/main/Scripts/install.sh | bash
Installed copies update themselves through Transfer → Check for Updates…