2026-08-22 09:26:15 +02:00
|
|
|
# dap-tui
|
|
|
|
|
|
|
|
|
|
A terminal UI for device sync of portable music players, rewritten from scratch
|
|
|
|
|
in Rust on top of [ratatui](https://ratatui.rs/).
|
|
|
|
|
|
|
|
|
|
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.
|
2026-08-22 09:57:53 +02:00
|
|
|
- **3 tabs** — Dashboard (per-player overview + settings, storage usage when
|
|
|
|
|
mounted, library + sync pipeline), Sync (live step pipeline + progress
|
2026-09-10 19:55:11 +02:00
|
|
|
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.
|
2026-08-22 09:57:53 +02:00
|
|
|
- **Live sync engine** — streams `rclone` and `rsync` output, parses progress
|
|
|
|
|
(byte-weighted via `--info=progress2`), NFC-aware diffing for
|
|
|
|
|
macOS→FAT32/Android devices, optional `fatsort` finalize for Mlove devices.
|
2026-09-10 19:35:03 +02:00
|
|
|
- **Abort-safe** — `a` kills 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.
|
2026-08-22 09:26:15 +02:00
|
|
|
- Reads the existing `~/.config/dap-sync/config.toml` (compatible with
|
|
|
|
|
`dap-sync`), persists last-sync state to `~/.local/share/dap-sync/sync-state.toml`.
|
|
|
|
|
|
|
|
|
|
## Install / run
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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
|
2026-09-10 19:43:55 +02:00
|
|
|
|
|
|
|
|
# install/update the `dap-tui` command (~/.cargo/bin) with the new build
|
|
|
|
|
cargo install --path . --force
|
|
|
|
|
dap-tui
|
2026-08-22 09:26:15 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Keybindings
|
|
|
|
|
|
|
|
|
|
| Key | Action |
|
|
|
|
|
| ------------ | ---------------------------- |
|
|
|
|
|
| `↑`/`↓` `j`/`k` | move selection |
|
|
|
|
|
| `s` / `Enter`| sync selected device |
|
|
|
|
|
| `a` | abort running sync |
|
2026-08-22 10:12:13 +02:00
|
|
|
| `m` | mount / unmount device (udisks2, no sudo) |
|
2026-08-22 09:57:53 +02:00
|
|
|
| `Tab` / `1-3`| switch tab |
|
2026-09-10 19:35:03 +02:00
|
|
|
| `r` | reload config + refresh mounts |
|
2026-08-22 09:26:15 +02:00
|
|
|
| `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:
|
|
|
|
|
|
|
|
|
|
```toml
|
|
|
|
|
[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"
|
|
|
|
|
```
|
|
|
|
|
|
2026-08-22 10:05:34 +02:00
|
|
|
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.
|
2026-08-22 09:26:15 +02:00
|
|
|
|
|
|
|
|
## How the sync pipeline works
|
|
|
|
|
|
|
|
|
|
1. **Mirror** — `rclone sync` (or local `rsync`) pulls the StorageBox source
|
2026-09-10 20:13:23 +02:00
|
|
|
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 as `Scanning remote · N items listed`.
|
2026-08-22 09:26:15 +02:00
|
|
|
2. **Diff** — the device is scanned and compared to the source with
|
|
|
|
|
NFC-normalised paths, size, and 2-second-rounded mtimes (FAT32 friendly).
|
|
|
|
|
3. **Sync** — stale files are removed first, then `rsync --files-from` copies
|
|
|
|
|
only what changed, with live per-file progress.
|
|
|
|
|
4. **Finalize** (mlove) — unmounts the volume and runs `fatsort` via
|
|
|
|
|
`sudo -n` to keep FAT directory order stable. If passwordless sudo is not
|
|
|
|
|
available the step is reported as a warning and the commands are printed.
|
|
|
|
|
|
2026-08-22 10:12:13 +02:00
|
|
|
## 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).
|
|
|
|
|
|
2026-09-10 19:35:03 +02:00
|
|
|
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.
|
|
|
|
|
|
2026-08-22 09:26:15 +02:00
|
|
|
## Development
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cargo test # parser + config tests
|
|
|
|
|
cargo run
|
|
|
|
|
```
|