v0.20.4: the song that wasn't there
trav couldn't play the first track of his playlist. Shuffle off gave him song two; shuffle on gave him a random one. The player was right both times: library.json named a song whose file was not on the device, and skipping an unplayable file advances one slot. Tapping any song that *is* there plays exactly that song, shuffle or not. The file was missing because a gvfs-MTP mount can hold a phantom directory -- one it lists happily while the device has no such folder. Every write into it fails EIO, and mkdir(exist_ok=True) sees the phantom and does nothing, so it never heals; only remounting clears it. That folder was new because Round 52's retag moved the file under a new artist. So a copy that raises OSError now costs that song, not the sync, and the song is left out of the m3u and library.json. Aborting cost 2,400 songs for one folder; counting it present anyway put a song in the manifest that isn't on the device, which is the bug you could hear. The names come back in the summary and a dialog rather than vanishing. Planning is a worker now. plan_andtunes_sync is pure and writes nothing, but it walks every file on the device, and over MTP that is thousands of round trips -- on the GUI thread it froze the window and GNOME offered to kill LinTunes, which is how a sync got force-quit halfway through. And the sync stopped going quiet at the end. "Album art 501/501" is emitted before the last album, and then _write_index, _ship_buttons and _prune_empty_dirs ran silently: 501 exists() stats over MTP, ~840 KB of writes and a full tree walk, with the progress line frozen. They report now, and _write_index reads Art/ in one listing instead of a stat per album. andTunes 0.2.2: the wheel scrolls the other way in lists, and smoothly -- a detent adds to a pixel debt that a Choreographer callback pays off a fraction per frame, so one detent eases to a stop and a fast spin blends into one movement instead of teleporting a row at a time. Volume is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SKXUgsBBwe3qaHEjeV8ubP
This commit is contained in:
@@ -347,6 +347,21 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
|
||||
device diff, which had been re-copying every such file over MTP every sync.
|
||||
`plan.stale` still carries the device's own spelling — that is what
|
||||
`_delete_stale` unlinks by.
|
||||
**A gvfs-MTP mount can hold a *phantom* directory** — one it lists happily
|
||||
while the device has no such folder — and every write into it fails `EIO`
|
||||
forever, because `mkdir(exist_ok=True)` sees the phantom and does nothing.
|
||||
Only remounting clears it (`gio mount -u mtp://…` then `gio mount`). Two
|
||||
rules came out of it (Round 55): a copy that raises `OSError` costs **that
|
||||
song**, not the sync (it lands in the `unwritable` summary list), and the
|
||||
song is then **left out of the m3u and `library.json`** — a manifest naming
|
||||
a file that isn't there is worse than a short one, because the app skips to
|
||||
the next track and the user sees the wrong song play. **Planning is a worker**
|
||||
too (`AndTunesPlanWorker`): `plan_andtunes_sync` is pure, but it walks every
|
||||
file on the device, and over MTP that froze the window long enough for GNOME
|
||||
to offer to kill LinTunes mid-sync. Anything after the last album
|
||||
(`_write_index`, buttons, pruning) must keep emitting progress — it was
|
||||
minutes of silence with the line stuck on `Album art 501/501`, and
|
||||
`_write_index` reads `Art/` in **one listing**, never a stat per album.
|
||||
Since Round 50 the app exists: `andtunes/app/` is plain Java against the
|
||||
Android framework, built by `andtunes/build.py` (aapt2 → javac → R8 →
|
||||
zipalign → apksigner) — **no Gradle, no Kotlin**, because platform 33 +
|
||||
|
||||
Reference in New Issue
Block a user