With SFTP remotes rclone lists the tree while transferring, so the `Transferred:` total grows during the run: the first ticks showed a single file's percentage (e.g. 2.9 MiB / 8.6 MiB, 35%) which then collapsed to 0% once the real 113 GiB total appeared. That is what looked like "progress per album". - pass --check-first: rclone finishes the remote listing before any transfer starts, so the byte total is final and the gauge is a true overall percentage for the whole mirror - while scanning, no bytes move yet, so the panel now shows `Scanning remote · 11,810 items listed` and `0 B / 113 GiB to transfer` (parsed from rclone's `Checks: … Listed N`) instead of a bogus 0% bar - once transfers start the display switches back to percentage + bytes + current file Verified against the real storagebox remote (capped with RCLONE_MAX_TRANSFER): scan ticks show listed counts climbing to 11,810 with the pending total, then transfers show `9.984 MiB / 113.477 GiB` with the final denominator and no per-file percentage jumps. Bump to 0.3.2.
4.8 KiB
dap-tui
A terminal UI for device sync of portable music players, rewritten from scratch in Rust on top of ratatui.
It mirrors the StorageBox masters library into your local music directory, then
syncs a device profile — the same pipeline as the Python dap-sync, now as a
living dashboard instead of a batch CLI.
Highlights
- Tokyo Night palette, rounded panels, gradient title rule, status & key bars.
- Devices sidebar — live mount status (green dot = mounted), firmware tags, last-sync age per device.
- 3 tabs — Dashboard (per-player overview + settings, storage usage when mounted, library + sync pipeline), Sync (live step pipeline + progress gauge with speed/ETA, timeline of the last run), Logs (color-coded, follow mode, warning/error counters).
- Responsive layout — short terminals drop the decorative sync-path card, narrow cards switch to compact labels/values and ellipsize long paths instead of clipping them mid-word.
- Live sync engine — streams
rcloneandrsyncoutput, parses progress (byte-weighted via--info=progress2), NFC-aware diffing for macOS→FAT32/Android devices, optionalfatsortfinalize for Mlove devices. - Abort-safe —
akills the running child process group cleanly; quitting while a sync runs aborts it first instead of leaving it orphaned. - Mount-aware — a path that exists but is not a real mount point is flagged and refused as a sync target, so a leftover mount directory can't silently receive a sync on the root filesystem.
- Reads the existing
~/.config/dap-sync/config.toml(compatible withdap-sync), persists last-sync state to~/.local/share/dap-sync/sync-state.toml.
Install / run
cargo build --release
./target/release/dap-tui # uses ~/.config/dap-sync/config.toml
./target/release/dap-tui --config /path/to/config.toml
./target/release/dap-tui --skip-mirror # skip the StorageBox → local mirror step
# install/update the `dap-tui` command (~/.cargo/bin) with the new build
cargo install --path . --force
dap-tui
Keybindings
| Key | Action |
|---|---|
↑/↓ j/k |
move selection |
s / Enter |
sync selected device |
a |
abort running sync |
m |
mount / unmount device (udisks2, no sudo) |
Tab / 1-3 |
switch tab |
r |
reload config + refresh mounts |
f |
toggle log follow |
u / d |
scroll logs |
x |
clear logs |
? / h |
help overlay |
q / Esc |
quit |
Configuration
dap-tui reads the standard dap-sync config format:
[paths]
local_music_dir = "~/Music"
local_hoerspiele_dir = "~/hoerspiele"
[storagebox]
source = "storagebox:masters"
exclude = ["/Music/**", ".DS_Store", ".localized"]
[[devices]]
name = "iPod Rockbox"
firmware = "rockbox"
mount_point = "/media/sebastian/IPOD"
music_folder = "Music"
Firmware types: apple (podkit command), rockbox, android, sony
(rsync to a mounted volume), mlove (rsync + fatsort). Devices with
content_type = "hoerspiele" sync against the hoerspiele library.
How the sync pipeline works
- Mirror —
rclone sync(or localrsync) pulls the StorageBox source into the local library. rclone runs with--check-first, so the remote listing completes before transfers start: the gauge then shows a true overall percentage instead of the growing-total estimate, and the listing phase is displayed asScanning remote · N items listed. - Diff — the device is scanned and compared to the source with NFC-normalised paths, size, and 2-second-rounded mtimes (FAT32 friendly).
- Sync — stale files are removed first, then
rsync --files-fromcopies only what changed, with live per-file progress. - Finalize (mlove) — unmounts the volume and runs
fatsortviasudo -nto keep FAT directory order stable. If passwordless sudo is not available the step is reported as a warning and the commands are printed.
Mounting
Devices are mounted/unmounted without sudo via udisks2:
m in the TUI mounts/unmounts the selected device, and a sync automatically
mounts the target first (resolved by mount-point label via lsblk). Only the
mlove fatsort step needs root (raw block device).
Mount status and sync targets are validated against the kernel mount table
(/proc/self/mountinfo), not just directory existence: if the configured
mount_point exists but is not actually mounted, dap-tui refuses to sync to
it rather than writing the library onto the root filesystem.
Development
cargo test # parser + config tests
cargo run