Files
lintunes/TASKS.md
trav 8dc33fa65e Round 17: task batch + cruft sweep (ratings, art download, MPRIS/exit/BT fixes)
Features:
- Rating hover dots: hovering a rating cell shows 5 clickable slots
  (RatingDelegate); click slot k sets k stars, clicking the current count
  clears. Undoable, library-only (ratings never rewrite music files).
- Search bar moved into the Library header strip, right of the Library
  button, so the tracklist top aligns with the playlist tree.
- Right-click "Download Album Art…": iTunes Search API (no key), off-thread
  fetch, preview/confirm dialog with Next Result, embeds via write_artwork
  + size refresh, invalidates the MPRIS art cache.

Fixes:
- MPRIS media keys: PropertiesChanged sent invalidated_properties as "av"
  instead of "as", so gsd-media-keys dropped it and never MRU-bumped
  lintunes (why the play/pause key kept waking stale players). Now an
  explicit QDBusArgument string array; loopback-verified sa{sv}as.
- Exit segfault: ordered Player.shutdown() (stop, clear source, detach
  buffer/audio outputs) from closeEvent/aboutToQuit; scripted run exits 0.
- BT zero-volume after pause/resume: volume re-applied on resume, device
  swap, and BufferedMedia (needs verify on the affected machine).

Cruft sweep:
- Tag writes filtered to EDITABLE_FIELDS; failures logged + surfaced in
  the status bar (was a swallowed print).
- O(n²) import fixed (location index + cached max track id); O(1)
  refresh/reveal via TrackTableModel row index.
- lastfm: scrobble-queue thread lock, login prefs write marshalled to the
  GUI thread, JSON/raise_for_status order fixed.
- Shared read_json/write_json; TableSettingsMixin dedupes view settings.

TASKS.md rewritten as a resumable board; tests in tests/test_round17.py
(251 total pass).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 11:36:11 -04:00

6.0 KiB
Raw Blame History

LinTunes — task board

Legend: [ ] todo · [~] in progress · [x] done. When a round closes, move its finished items to tasks-done.md.

Round 17 (current) — TASKS.md batch + cruft cleanup

Plan reference: ~/.claude/plans/can-you-take-a-drifting-sonnet.md. Tests land in tests/test_round17.py.

Phase 1 — cruft & correctness quick wins

  • 1a. Tag-write filter: _write_track_tags only writes fields in tagging.EDITABLE_FIELDS; rating/size edits become library-JSON-only and no longer rewrite music files (library_manager.py)
  • 1b. Replace print() with logging; new tag_write_failed signal surfaced in the status bar (library_manager.py, file_importer.py, main_window.py, main.py)
  • 1c. O(n²) import fix: {location: track} index per import_paths + cached _max_track_id for O(1) new_track_id (file_importer.py, library_manager.py)
  • 1d. TrackTableModel._row_by_id index → O(1) refresh_track / reveal_track (track_table.py)
  • 1e. threading.Lock around the scrobble-queue load/mutate/save (lastfm.py)
  • 1f. Last.fm login: prefs write marshalled to the GUI thread via an internal signal (lastfm.py)
  • 1g. Shared read_json/write_json in json_storage.py; drop the duplicates in conflict_resolver.py
  • 1h. lastfm._call: try JSON first, fall back to raise_for_status() on non-JSON bodies

Phases 24 — player & desktop integration

  • Task F: Bluetooth zero-volume after pause/resume — factored _apply_volume(); re-applied on resume, after setDevice, and on BufferedMedia (player.py)
  • verify Task F on the Bluetooth machine: pause ≥30 s on BT headphones, resume, must be audible without seeking
  • Task E: exit segfault — idempotent Player.shutdown() (stop → clear source → detach buffer/audio outputs → disconnect QMediaDevices), called from closeEvent + aboutToQuit. Verified 2026-07-02: scripted run (import → play to end → close) exits 0, no segfault.
  • Task C: MPRIS play/pause commandeering — root cause found & fixed: _notify sent PropertiesChanged with invalidated_properties marshalled as av instead of as, so gsd-media-keys dropped it and never bumped LinTunes in its media-key MRU. Now an explicit empty string-array via QDBusArgument (mpris.py). Verified 2026-07-02 by D-Bus loopback: old code's signature was sa{sv}av, new is sa{sv}as (test_round17).
  • verify Task C in practice: play in LinTunes with a stale YouTube tab around; the media key should control only LinTunes. Fallbacks if GNOME still misroutes (documented, not built): org.gnome.SettingsDaemon.MediaKeys.GrabMediaPlayerKeys, bus-name re-registration on play.

Phases 57 — features

  • Task B: search bar moved into the Library header strip (_top_strip), directly right of the sidebar Library button, so the browse/tracklist top aligns with the playlist tree (library_view.py)
  • Task A: rating hover dots — hovering a rating cell shows 5 slots (★ where rated, • where not); click slot k sets k stars; clicking the current rating clears it. RatingDelegate + rating_edited signal, wired to manager.edit_track_fields in both views (track_table.py, library_view.py, playlist_view.py). Rating is library-only (no music-file rewrite), undoable with Ctrl+Z.
  • Task D: right-click "Download Album Art…" — iTunes Search API (no key; upscale artworkUrl100 → 600x600), off-thread fetch, confirmation dialog with preview + Next Result, embed via tagging.write_artwork + size refresh, invalidate MPRIS art cache (new art_search.py, new gui/album_art_dialog.py, track_table.py, main_window.py, mpris.py)
  • verify Tasks A/B/D by eye in the running app (hover/click ratings, search-bar alignment, art download on a real album)

Phase 8

  • TableSettingsMixin: dedupe sort/columns/width persistence between LibraryView and PlaylistView (new gui/table_settings.py)

Standalone maintenance (manual, not code work)

  • Artwork recovery against the LIVE data dir (embeds the 790 album-propagated covers; gets art-bearing coverage to 100%): 1. python3 scripts/audit_artwork.py --data-dir <data> (read-only re-check) 2. python3 scripts/recover_artwork.py --data-dir <data> --dry-run → review 3. re-run with --write; audit_artwork.py again to confirm Note: lintunes/itc.py is a live dependency of recover_artwork.py (tests in test_round9.py) — do not delete as "unused".

Parked / deferred

  • Smart playlists Phase 2: nested-group editing UI in the criteria dialog (import + evaluation of nested groups already works; imported nested playlists are read-only until then). Big change, on hold.
  • Live-sync v2: per-playlist tombstones (so a delete on one machine isn't resurrected by the union merge during a simultaneous edit) + optional per-change accept/refuse review. Low priority — trav doesn't edit on both machines at once; v1 handles sequential use.
  • Data-dir location as an in-app preference (currently only --data-dir / --save-config).
  • Album-art grid view: a browsable grid of album covers for the library (the artwork-coverage investigation that motivated it is answered & tooled — see Standalone maintenance above).
  • Off-thread artwork reads: SidebarArt.set_track and the Get Info dialog read embedded art synchronously on the GUI thread (fine for local files; worth revisiting for large FLACs).
  • Progress UI (or background thread) for multi-selection tag writes in edit_tracks_fields — a big batch currently blocks the UI.
  • More aggressive media-key commandeering beyond the Task C fix, if the real-machine verification shows GNOME still routing keys elsewhere.