Commit Graph
3 Commits
Author SHA1 Message Date
trav 1280c89015 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.

Claude-Session: https://claude.ai/code/session_018ZCVBTJRFJ2XfMshu2gtUv
2026-09-11 15:20:44 -05:00
trav d335e9144b v0.18.0: paste a link, get the song
File > Import from URL... runs trav's `song` helper (yt-dlp --extract-audio
--audio-format mp3) from inside the app. Everything at the link downloads to
a private temp dir and is imported as each song lands. "and add to current
playlist?" puts the batch above the selected song, else at the end of the
playing playlist, else at the end of the shown one, in the link's order.
Each song goes straight into Identify Track. With no AcoustID key, the
proposal comes from the filename alone.

Album art now opens on a single click, in a window shaped like the cover
and as big as the screen allows.

Also fixed the dev fixture's play journal, which crashed startup (wrong
shape) and would have been ignored anyway (wrong keys). The loader now
skips a journal it can't read instead of aborting.

Claude-Session: https://claude.ai/code/session_01R5m9mXFPHNro78BdD69mG2
2026-09-10 23:44:45 -05:00
trav 3214bb7d96 v0.9.0: play counts that can't conflict
The merge windows kept coming because finishing a track rewrote all 15 MB of
library.json. Both machines did that, so Syncthing saw two edits to one big
file between syncs and produced a conflict file roughly once per song — and
the merge then took max() of the two counts, discarding whichever side had
played less. The .resolved/ backups showed the last nine library.json merges
were ~98% play counts plus exactly one real edit, with the same ~45 tracks
disagreeing every time and the count only creeping down over a day.

library.json now holds only a base count. Each machine owns
plays/<machine-id>.json with its own per-track totals, and the effective count
is base + the sum of every journal. Only the owner writes its journal, so play
data can't conflict; the journal stores totals rather than an append log, so
there's no compaction step to double-count in; and a machine still on older
code keeps bumping its own base, which stays additive with our journal.

Two places carry the whole hazard, and both are pinned by tests: save_tracks
writes journal.base_fields(), never the Track's effective count, and
PlayJournal.load must be given a base-valued library — which is why
reload_from_disk folds the journals onto the disk copy before reconciling.

On the real 21k library: three plays write 83 bytes and leave library.json
untouched.

Claude-Session: https://claude.ai/code/session_01M8Mzze7shr5pZoEgKQU5NW
2026-08-20 20:42:07 -04:00