You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
expressport: copy audio data only, read ahead of the stick writes, and look for ._ files only with --prune #196
write_track in crates/baken-export/src/lib.rs copies audio with std::fs::copy. On macOS that is fcopyfile(COPYFILE_METADATA | COPYFILE_DATA) (Rust std, library/std/src/sys/fs/unix.rs): data plus extended attributes, ACLs, mode and times.
Every xattr on the source (com.apple.quarantine on a downloaded track, Finder tags, com.apple.lastuseddate#PS) becomes a ._X AppleDouble file next to X on a FAT or exFAT stick. On macOS 26 (FSKit) that ._ file costs a create (27 ms), a write and close (25 ms) and, at the end of the run, an unlink. The mode and time copies are further round trips the stick does not need. Then remove_apple_double walks all of Contents/ and PIONEER/USBANLZ after any run that copied a file, at about 19 ms per freshly written directory: about 3 s for a 62-track export, about 50 s for 1100 tracks. In the sandboxed Mac app the walk cannot remove anything (expressport: a sandboxed caller cannot remove ._ files on a FAT stick, so export fails after export.pdb is written and prune stops partway #192), so there it is pure cost.
fcopyfile reads a piece, then writes it, in turn. With a fast source that does not matter, but with the library on a NAS, an HDD or over Wi-Fi, reading and writing never overlap: at 13 MB/s read and 25 MB/s write the copy runs at about 8.6 MB/s instead of 13.
Measured on 2026-09-29
SanDisk USB 3.2 Gen1 stick, FAT32 on MBR, mounted by FSKit on macOS 26, source on an exFAT USB SSD, one 57 MB AIFF copied 3 times per variant in round robin, fsync included. Raw stick speed that day: 17 to 23 MB/s with dd cold, 25 to 30 MB/s warmed up.
Copy
MB/s
fcopyfile with metadata (today)
24.8
fcopyfile data only
25.5
read/write loop, 64 KiB
25.5
read/write loop, 1 MiB
26.8
loop, 1 MiB, F_PREALLOCATE (contiguous)
27.1
loop, 1 MiB, F_NOCACHE on the destination
15.6
reader thread, 3 x 4 MiB in flight
27.2
The copy primitive is not what limits an export: every variant sits at the stick's own speed. The source had no xattrs, so the ._ cost is not in this table; it applies to a library on APFS.
Per-track fixed costs on the same stick: create_dir_all of a new P…/hash directory 154 ms, File::create 27 ms per file, write plus close 23 to 30 ms per file whether it is 11 KB or 224 KB, fs::read of a missing file 0.1 ms, stat 0 ms, unlink of a 4 KB ._ file 0.6 ms, read_dir of a just written directory 19 ms. Writing the analysis files from a second thread while the audio copies gains nothing (10.8 s against 11.1 s for 4 tracks): FSKit serves the volume one operation at a time.
Proposal
One copy_audio(src, dst) in baken-export replacing both std::fs::copy calls in write_track:
Opens the source, creates the destination and copies data only (no xattr, ACL, mode or time), with a reader thread feeding a bounded channel of 4 MiB buffers (3 in flight, 12 MiB) to the writing thread. Neutral on a fast source (27.2 against 26.8 MB/s); on a slow one reads overlap writes.
The CLI then never creates ._ files itself, so remove_apple_double on Contents/ and PIONEER/USBANLZ runs only with --prune. The two small non-recursive walks of PIONEER/ and PIONEER/rekordbox stay. Files a sandboxed app creates still get their ._ files from the system; those are counted in Report::apple_double_kept and cannot be removed anyway.
--cdjsafe (see the transcode issue) copies its transcoded temp file through the same function.
Not worth doing, measured: F_PREALLOCATE (works on FSKit FAT32, no speed gain, and FSKit does not support F_LOG2PHYS, so contiguity cannot even be checked), F_NOCACHE on the destination (40% slower), chunk sizes between 64 KiB and 1 MiB (noise), a second writer thread for the analysis files, dropping the fs::read probe in write_anlz (0.1 ms).
Found while implementing (PR 200): macOS adds com.apple.provenance to every file a process running under a third-party app creates. A file written from this session (under Zed) has it, on the internal disk and on the stick, where FAT stores it as a ._ file the moment the file is created; a file written by a launchd job has none. So the CLI run from Zed, Ghostty, iTerm2 and the like creates ._ files for everything it writes, whatever the copy does, and the end-of-run walk has to stay (it already runs only after a run that wrote something). The copy is still data only, so a source's own xattrs no longer travel.
Caveat on the per-file numbers above: they were taken from such a process, so every file create included its ._ companion. From Terminal.app (an Apple app) they may be lower; not measured.
What happens today
write_trackincrates/baken-export/src/lib.rscopies audio withstd::fs::copy. On macOS that isfcopyfile(COPYFILE_METADATA | COPYFILE_DATA)(Rust std,library/std/src/sys/fs/unix.rs): data plus extended attributes, ACLs, mode and times.com.apple.quarantineon a downloaded track, Finder tags,com.apple.lastuseddate#PS) becomes a._XAppleDouble file next toXon a FAT or exFAT stick. On macOS 26 (FSKit) that._file costs a create (27 ms), a write and close (25 ms) and, at the end of the run, an unlink. The mode and time copies are further round trips the stick does not need. Thenremove_apple_doublewalks all ofContents/andPIONEER/USBANLZafter any run that copied a file, at about 19 ms per freshly written directory: about 3 s for a 62-track export, about 50 s for 1100 tracks. In the sandboxed Mac app the walk cannot remove anything (expressport: a sandboxed caller cannot remove ._ files on a FAT stick, so export fails after export.pdb is written and prune stops partway #192), so there it is pure cost.fcopyfilereads a piece, then writes it, in turn. With a fast source that does not matter, but with the library on a NAS, an HDD or over Wi-Fi, reading and writing never overlap: at 13 MB/s read and 25 MB/s write the copy runs at about 8.6 MB/s instead of 13.Measured on 2026-09-29
SanDisk USB 3.2 Gen1 stick, FAT32 on MBR, mounted by FSKit on macOS 26, source on an exFAT USB SSD, one 57 MB AIFF copied 3 times per variant in round robin, fsync included. Raw stick speed that day: 17 to 23 MB/s with
ddcold, 25 to 30 MB/s warmed up.fcopyfilewith metadata (today)fcopyfiledata onlyF_PREALLOCATE(contiguous)F_NOCACHEon the destinationThe copy primitive is not what limits an export: every variant sits at the stick's own speed. The source had no xattrs, so the
._cost is not in this table; it applies to a library on APFS.Per-track fixed costs on the same stick:
create_dir_allof a newP…/hashdirectory 154 ms,File::create27 ms per file, write plus close 23 to 30 ms per file whether it is 11 KB or 224 KB,fs::readof a missing file 0.1 ms,stat0 ms, unlink of a 4 KB._file 0.6 ms,read_dirof a just written directory 19 ms. Writing the analysis files from a second thread while the audio copies gains nothing (10.8 s against 11.1 s for 4 tracks): FSKit serves the volume one operation at a time.Proposal
One
copy_audio(src, dst)inbaken-exportreplacing bothstd::fs::copycalls inwrite_track:._files itself, soremove_apple_doubleonContents/andPIONEER/USBANLZruns only with--prune. The two small non-recursive walks ofPIONEER/andPIONEER/rekordboxstay. Files a sandboxed app creates still get their._files from the system; those are counted inReport::apple_double_keptand cannot be removed anyway.--cdjsafe(see the transcode issue) copies its transcoded temp file through the same function.Not worth doing, measured:
F_PREALLOCATE(works on FSKit FAT32, no speed gain, and FSKit does not supportF_LOG2PHYS, so contiguity cannot even be checked),F_NOCACHEon the destination (40% slower), chunk sizes between 64 KiB and 1 MiB (noise), a second writer thread for the analysis files, dropping thefs::readprobe inwrite_anlz(0.1 ms).