v0.20.0: the Rabbit's plays come home
andTunes phase 3. The R1 is now one more machine in the Round 38 play-count model, and nothing lands on it that Android can't play. Play counts: andTunes 0.2.0 counts a play on a natural finish (LinTunes' rule) and keeps Music/andTunes/plays/andtunes-<install id>.json in exactly the per-machine-totals journal shape, written beside the old one and renamed over it. Each sync first folds it into <data_dir>/plays/ with a new play_journal.merge_totals, the per-track max: the file now has two homes and both desktops may bring it back, so the max converges and an older copy can never pull a count down. Nothing is written when nothing moved, and PlayJournal.load needed no change. Format gate: the planner reuses export/web_support.conversion_for outright, since Android's MediaPlayer decodes the browser's set. FairPlay is refused and reported; ALAC, AIFF and oddities land as FLAC, converted once into a per-track cache and size-diffed after that. With no ffmpeg, the export's "sync without them?" question. A file ffmpeg can't read costs that song, not the sync. "Sync Playlist to Rabbit (Auxio)" is retired from the menu; device_sync's helpers stay because export and andTunes import them. The dev fixture grew a real ALAC track, a Protected AAC .m4p and an "Odd Formats" playlist. Verified on the R1 with it: AIFF and ALAC arrived as FLAC and played, the FairPlay track was refused, four plays came back on the next sync and each track's effective count rose by exactly one, and a third sync copied nothing. Found along the way: USB re-enumeration can wedge gvfsd-mtp, after which anything touching the mount (find_device, the GUI tests) hangs in an uninterruptible wait. CLAUDE.md now has the recovery. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ZCVBTJRFJ2XfMshu2gtUv
This commit is contained in:
@@ -289,11 +289,12 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
|
||||
copystat and never trust mtimes (diff by name+size). Sync owns exactly
|
||||
`Music/<Playlist Name>/` on the device (creates/overwrites/deletes there,
|
||||
plus an Auxio-importable `.m3u`); it never deletes outside that folder and
|
||||
only ever *reads* local library files. Since Round 47 this is the *older*
|
||||
of two syncs — kept on the menu as "Sync Playlist to Rabbit (Auxio)" until
|
||||
the andTunes app can play a song — and its `sanitize_name` / `track_display`
|
||||
/ `track_filename` / `build_m3u` / `CHUNK` are imported by both
|
||||
`export/exporter.py` and `andtunes/`, so their signatures are load-bearing.
|
||||
only ever *reads* local library files. Its menu item ("Sync Playlist to
|
||||
Rabbit (Auxio)") was **retired in Round 51**, once andTunes could play what
|
||||
it syncs; the module stays because `find_device` and its `sanitize_name` /
|
||||
`track_display` / `track_filename` / `build_m3u` / `CHUNK` are imported by
|
||||
both `export/exporter.py` and `andtunes/`, so their signatures are
|
||||
load-bearing.
|
||||
|
||||
- **`lintunes/andtunes/`** — the desktop half of andTunes, a music player for
|
||||
the Rabbit R1 (Round 47; the app itself lives in `andtunes/` at the repo
|
||||
@@ -343,6 +344,23 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
|
||||
where the file is missing** — those names are a contract with the app's
|
||||
`Ui.button()`, which prefers the device copy. `proguard.pro` keeps no
|
||||
debug attributes because R8 8.2 NPEs on javac 21's.
|
||||
Round 51 closed the loop. The planner runs every track through
|
||||
`export/web_support.conversion_for` (Android decodes the browser set), so
|
||||
FairPlay is refused and ALAC/AIFF land as `.flac` — converted once into
|
||||
`$XDG_CACHE_HOME/lintunes/andtunes-flac/<track id>.flac` and size-diffed
|
||||
from there on, so a converted song isn't re-copied every sync. And the app
|
||||
keeps `plays/andtunes-<install id>.json` in the play-journal shape, counted
|
||||
on a natural finish like `Player`; `plays.bring_back` folds it into
|
||||
`<data_dir>/plays/` with `play_journal.merge_totals` (per-track max — that
|
||||
file has two homes and both desktops may bring it back) **before** anything
|
||||
is copied, writing nothing when nothing moved. `PlayJournal.load` then
|
||||
treats the R1 as one more machine.
|
||||
**A wedged gvfs-MTP mount blocks in uninterruptible FUSE waits**: `timeout`
|
||||
can't kill a process stuck on it, and `find_device()` — called on the GUI
|
||||
thread every time the Connections menu opens, and by the GUI tests —
|
||||
freezes with it. USB re-enumeration (lock/unlock with `mtp,adb`, an
|
||||
`adb install`) is what wedged it in Round 51. Recovery: `kill` the
|
||||
`gvfsd-mtp` process, then `gio mount mtp://<device>/`.
|
||||
|
||||
- **`lintunes/export/`** — `File → Export Playlist…` (also on a playlist's
|
||||
right-click menu). A sibling of `device_sync`, reusing its filename helpers
|
||||
|
||||
Reference in New Issue
Block a user