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:
2026-09-11 15:20:44 -05:00
co-authored by Claude Opus 5
parent 9483d8951b
commit 81b7f6767c
17 changed files with 864 additions and 127 deletions
+49
View File
@@ -1,5 +1,54 @@
## Done
### Round 51 (2026-09-11) — counts come home, and only playable files go (v0.20.0)
andTunes phase 3. The Rabbit is now one more machine in the play-count
model, and nothing lands on it that Android can't play.
- [x] **Play counts come back** (andTunes 0.2.0). A natural finish counts a
play, which is LinTunes' rule, and LinTunes records no skips either.
The app keeps `Music/andTunes/plays/andtunes-<install id>.json` in the
Round 38 per-machine-totals shape and writes it beside the old one then
renames it over, so a sync never reads half a file. Each sync first
folds it into `<data_dir>/plays/` (`andtunes/plays.py`), using a new
`play_journal.merge_totals` (the per-track max). That 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 at all.
- [x] **Format gate.** The planner reuses `export/web_support.conversion_for`
outright, because Android's MediaPlayer decodes the browser's set.
FairPlay is refused and reported, and ALAC, AIFF and oddities land as
`.flac`. Each conversion is cached at
`$XDG_CACHE_HOME/lintunes/andtunes-flac/<track id>.flac`, so after the
first sync it's an ordinary size diff and is never re-copied. With no
ffmpeg, the same "sync without them?" question as export. A file
ffmpeg can't read costs that song, not the sync.
- [x] **"Sync Playlist to Rabbit (Auxio)" retired** from the Connections
menu, along with its handlers and worker plumbing. `device_sync`'s
helpers stay, because export and andTunes import them.
- [x] **The dev fixture grew the shapes the gate needs**: a real ALAC
`.m4a`, a "Protected AAC" `.m4p`, and an "Odd Formats" playlist (AIFF,
WAV, OGG, ALAC, FairPlay), ticked for sync by default.
- [x] **Found while verifying: a wedged gvfs-MTP mount hangs everything that
touches it.** A USB re-enumeration left `gvfsd-mtp` holding a dead
session. Every `ls` then sat in an uninterruptible FUSE wait that even
`timeout` couldn't kill. The dev sync and the GUI tests (which call the
real `find_device()`) both hung with it. Recovery was killing
`gvfsd-mtp` and running `gio mount mtp://…/`; the steps are now in
CLAUDE.md. It also explains the earlier 3-minute test runs: the full
suite takes 11 s against a healthy mount.
- [x] Verified with the dev library on the R1:
- AIFF → `01 Reel One.flac` and ALAC → `03 Reel Three.flac` on the
device; the FairPlay track was refused; 18 stale files from the old
fixture were pruned; the default buttons were shipped.
- All four Odd Formats songs played through on the R1.
- The next sync brought back `plays: 4`, and each track's effective
count went up by exactly one, with the device's `last_played`.
- A third sync copied nothing.
- Cold start 338–347 ms.
`tests/test_round51.py` covers the gate, the cache, `bring_back` and
the fold; the full suite passes.
### Round 50 (2026-09-11) — andTunes plays music on the Rabbit (v0.19.0)
The Android half of andTunes had been paused on a 1 GB toolchain download.