Commit Graph
6669 Commits
Author SHA1 Message Date
vicrodh c4d3fc7e3b chore(deps): cargo update — 194 semver-compatible bumps
cc
2026-08-29 08:23:36 -06:00
vicrodh 51daf760b7 docs+ci: two glibc floors — x86_64 2.35, arm64 desktop 2.39, qbzd 2.35 everywhere
Decided 2026-08-29: the arm64 Qt build only works on ubuntu-24.04-arm
(aqt's arm64 host tools segfault on 22.04-arm), so the arm64 desktop
app requires glibc 2.39; qbzd keeps building on 22.04-arm at 2.35 so a
trimmed Pi OS bookworm image stays a valid target. The floor gate asserts
per arch instead of warning; README states both floors.

cc
2026-08-29 08:08:14 -06:00
vicrodh a1763df250 merge: release workflows green on every platform (desktop-entry gate, Icon Composer icon, arm64 runner)
cc
2026-08-29 07:54:17 -06:00
vicrodh d66c993ade ci(macos): accept @rpath Qt install names with a bundle-relative LC_RPATH
Qt 6.9's macdeployqt leaves @rpath/Qt*.framework references resolved
through LC_RPATH @executable_path/../Frameworks; the old assert only
accepted a literal @executable_path and failed a correct bundle. Reject
absolute paths, require the rpath, and let the offscreen smoke prove it.

cc
2026-08-28 21:41:15 -06:00
vicrodh e70e5e4ab9 ci(linux-arm64): experiment — build aarch64 on ubuntu-24.04-arm (aqt qmake segfaults on 22.04-arm)
Both 6.8.3 and 6.9.3 arm64 qmake die with SIGSEGV on ubuntu-22.04-arm
with no missing libraries. Try the 24.04-arm image; the glibc floor gate
warns instead of failing for aarch64 until the floor is decided.

cc
2026-08-28 21:13:51 -06:00
vicrodh 860f1ad67f ci(linux): the AppDir check no longer expects .DirIcon (appimagetool writes it)
cc
2026-08-28 21:10:49 -06:00
vicrodh fd980440af ci(linux-arm64): Qt 6.9.3 for aarch64 (aqt's 6.8.3 arm64 qmake segfaults on the runner)
The QMAKE handoff exposed the real failure: aqt's linux_gcc_arm64 6.8.3
qmake dies with SIGSEGV on ubuntu-22.04-arm, which cxx-qt-build reported
as QtMissing. The arm line pins 6.9.3 (QT_VERSION_LINUX_ARM64 overrides);
x86_64 keeps the 6.8 LTS floor. file/ldd diagnostics run before the
qmake query so the next such failure names itself.

cc
2026-08-28 21:10:04 -06:00
vicrodh 37a1a75de1 ci(linux): pass QMAKE to cxx-qt-build; fix(player): macOS match covers StreamType::Direct
aarch64 runner: cxx-qt-build reported QtMissing with a correct
QT_ROOT_DIR — set QMAKE/CMAKE_PREFIX_PATH explicitly (as macOS/Windows
already do) and print the qmake version before the build.

macOS: StreamType::output_sample_rate (macOS-only) matched Rodio only;
Direct is intentionally not cfg-gated, so the macOS build failed with
E0004. A direct sink answers with its opened rate.

cc
2026-08-28 21:03:47 -06:00
vicrodh 7b05ef55ae fix(listen-log): a gapless hand-off closes the previous row as natural, not skip
Under gapless the engine never reports a not-playing tick between two
tracks, so the poll loop's track_ended edge does not fire and the next
track's edge found the previous row still open and closed it as skip
(smoke: 229 s of a 230 s track recorded as skip). The Qt edge now uses
the tracker's inference (last observed position within 2 s of the
duration = natural), the same rule qbzd already applies.

cc
2026-08-28 20:50:26 -06:00
vicrodh 8f35809df7 ci(release): desktop-entry icon gate; Icon Composer app icon on macOS
Linux: scripts/packaging/desktop_entry_check.sh asserts the MPRIS
DesktopEntry constant against the recipes (nfpm basename + Icon=,
StartupWMClass, reverse-DNS basenames) and against the built deb/rpm/
AppDir. The AppDir's desktop file now carries the reverse-DNS basename
so an integrated AppImage resolves the media-widget icon too. This
invariant regressed more than once; it is a gate now, not a rename.

macOS: compile packaging/icons/QBZ.icon (Icon Composer, issue #712) with
Xcode 26's actool into Assets.car + CFBundleIconName=QBZ; icon.icns stays
as CFBundleIconFile for older macOS. The Intel leg moves to macos-26-intel
so both legs carry actool.

cc
2026-08-28 20:45:55 -06:00
vicrodh a3161254e1 merge: listen log, cross-source identity and §12.0 debts
cc
2026-08-28 20:45:38 -06:00
vicrodh ed10a49562 chore(source): set the new QueueTrack identity fields in the remaining constructors
Plex and the Jellyfin/Subsonic test fixtures: None (the Plex cached row
does not carry the mbid yet).

cc
2026-08-28 20:31:19 -06:00
vicrodh 2208ca7a9b feat(audio): loudness_cache gains a content_key column (table only)
An ISRC or fingerprint column plus a partial index, with
set_content_key / get_by_content_key helpers, so a local or Plex copy of
the same recording can reuse an analysis. The analyser and every audio
path are untouched; set/get keep their track_id contract.

cc
2026-08-28 20:31:05 -06:00
vicrodh 4e41e92347 feat(plex): request includeGuids and keep the recording MBID
Section listings ask for includeGuids=1 and the track parser reads the
<Guid id="mbid://…"/> child (only the Plex Music agent writes it; other
schemes are ignored). Stored in a new plex_cache_tracks.recording_mbid
column (additive; COALESCE on upsert) and read back by the PlexTrack
mappers. PlexCachedTrack and the qbz-source Plex constructor do not
carry it yet — the catalog projection does.

cc
2026-08-28 20:31:05 -06:00
vicrodh e7135a424a feat(media-servers): capture the recording MBID and ISRC from Jellyfin and Subsonic
Jellyfin requests Fields=ProviderIds on both listing passes and maps
ProviderIds.MusicBrainzTrack (the recording id) into JellyfinTrack;
Subsonic reads OpenSubsonic musicBrainzId and the first isrc. Both land
in two additive remote_cache_tracks columns (isrc, recording_mbid), kept
across upserts with COALESCE so a known id is never overwritten by a
server that stops sending it, and travel to the queue through
qbz-source's remote constructor.

cc
2026-08-28 20:31:05 -06:00
vicrodh 702180cdea feat(library): read ISRC and MusicBrainz ids from tags; project them to the catalog
The scanner now keeps the identity tags Picard writes (ISRC/TSRC and the
MusicBrainz recording, track, release, release-group and artist ids —
the same lofty ItemKeys the tag writer already knows) in six additive
local_tracks columns, migrated idempotently (also after the sample_rate
table rebuild) and indexed on isrc and the recording id. Rows fill on
the next scan. The catalog projection (legacy.rs) stops hardcoding
isrc / musicbrainz_recording_id to None for local, remote and Plex rows
(the upsert already bound both), treating an empty string as no id.
qbz-source stamps both keys on local queue tracks.

cc
2026-08-28 20:31:05 -06:00
vicrodh c17f932d6c test: require the listen_log suite in the gate
cargo-test.sh and test-crates.yml assert qbz-app::listen_log lists at
least 25 tests and passes, so a refactor that drops the module's tests
fails loudly instead of shrinking coverage. Bounded: the binary is
already built by the workspace run.

cc
2026-08-28 20:30:43 -06:00
vicrodh 9b14740e32 feat(settings): Listening history toggle and clear; history read gate
Settings > Integrations > PRIVACY: a Listening history toggle (default
ON; the state lives in the per-user listen_meta, so it follows the
account) and a confirmed Clear (DELETE + VACUUM). Strings in all eight
catalogs; README names where the data lives.

ScrobblerSettings::history_read_allowed(service) = *_active(): the
scrobbling switch now governs reading a service's history for
Recommendations as well as sending to it, and its Settings text says
so. recommendations_qt uses it instead of *_is_authed().

cc
2026-08-28 20:30:43 -06:00
vicrodh 3e060d6535 feat(qbzd): listen log subscriber beside the scrobbler
A CoreEvent-bus sibling of scrobble_engine WITHOUT its enabled gate: the
log is local and on by default, so a headless streamer finally records
history. TrackStarted opens a row and closes the previous one with an
inferred reason (natural within 2 s of the end, skip otherwise — nobody
emits TrackEnded); PositionUpdated feeds the accumulator; Stopped closes
with the same inference. origin_id = qbzd:<hostname>; the shutdown path
aborts the task and closes the open row.

cc
2026-08-28 20:30:43 -06:00
vicrodh b38cb34f08 feat(qt): listen log tracker on the playback poll loop
listen_log_qt binds the per-user ListenLogger at login/restore/offline
entry and drops it at logout. Hooks: the de-duped track edge opens a row
(closing the previous as skip), every 1 Hz tick feeds the accumulator,
the natural end-of-track predicate closes as natural, the stream-failure
toast closes as error, three consecutive empty ticks are the stop, and
main.rs closes the row in flight as shutdown after the event loop (and
on the Windows session-commit path). Cast and peer QConnect stay out.
Ephemeral ids never reach the log (same guard as recently_qt).

cc
2026-08-28 20:30:43 -06:00
vicrodh ef665dcc5c feat(models): QueueTrack carries isrc/recording_mbid; Track models audio_info
QueueTrack gains two additive, serde-default identity keys (the ISRC and
the MusicBrainz recording id) that the listen log stamps; every
constructor in the workspace sets them (Qobuz-backed ones from
Track.isrc, local ones from the scanned tags, the rest None). They are
JOIN keys, not the row identity: numeric ids collide across sources, so
(source, native item id) stays the identity.

Track.audio_info (replaygain_track_gain / peak on DiscoverAudioInfo) is
modelled from the captured search envelopes; no reader yet and no
popularity field (0 in every sample).

cc
2026-08-28 20:30:42 -06:00
vicrodh 5bd1009ca1 feat(listen-log): per-user listen event store, tracker and reading rules
New qbz-app::listen_log (contract §12.1): listen_events with PK = event
(never the track), snapshot text per row, source + native item id,
context, stream facts; listen_meta with schema_version, origin_id,
paused, retention_days (unlimited). The tracker is a pure state machine:
played_ms accumulates only while playing from monotonic deltas <= 5 s,
so pauses, seeks and stalls add nothing; flush every 10 s of play; rows
close as natural/skip/stop/error/shutdown and orphans close as shutdown
on the next open. No threshold at write time: is_play / is_skip are
pure reader rules with boundary tests. ListenLogger is the async facade
(spawn_blocking everywhere); paused writes nothing, clear is
DELETE + VACUUM + checkpoint. 29 tests.

cc
2026-08-28 20:30:00 -06:00
vicrodh 6caab7b71a feat(reco): identity guardrail replaces the genre blocklist; rank by score
- INCOMPATIBLE_GENRES / INCOMPATIBLE_TITLE_KEYWORDS removed: a global
  blocklist papered over an identity bug (name search returning a
  homonym) and punished every user's taste for it.
- artist_guardrail: ArtistLookup seam (mockable), id before text
  (qobuz:{id} nodes resolve by id; MBIDs hit the mb_qobuz_artist_match
  cache before search_artists), validation relative to the playlist
  seeds (shared level-1 genre root or a shared MusicBrainz tag), homonym
  tie-break by seed neighbourhood / label, reject when no signal exists.
- suggestions: stable sort by similarity_score desc, dedup keeping the
  higher score, THEN truncate (the shuffle threw the score away and the
  dedup kept the first hit while claiming the best).
- Eight network-free tests cover the rules; rand is now unused here.

cx
2026-08-28 20:30:00 -06:00
vicrodh 6587201a1c fix(qbzd): log loudly when the artist-vector store fails to open
The boot comment claimed suggestions work un-cached without the store;
suggestions.rs and builder.rs both answer "No active session" when it
is None. Warn on failure and say what the symptom will be.

cc
2026-08-28 19:54:47 -06:00
vicrodh 6ca219d90e fix(history): prune Most Played on folder removal; GC stale radio sessions
Removing a library folder pruned Recently Played but not
album_play_history.db, so Most Played kept cards onto deleted tracks.
radio_history had no delete path at all; sessions idle for more than
30 days (by last played_at, so a running radio is never pulled) are
dropped on open.

cc
2026-08-28 19:54:47 -06:00
vicrodh 6a99539537 fix(playlist-manager): read play counts from playlist_play_history.db
playlist_stats.play_count had no writer anywhere in the workspace
(increment_playlist_play_count: zero callers), so the badge and the
Play Count sort were 0 for everyone. Derive the counts from the store
the track-start edge actually writes; the library table is untouched.

cc
2026-08-28 19:54:47 -06:00
vicrodh ae9306988e fix(offline-cache): touch last_accessed_at when a cached track plays
OfflineCacheDb::touch had no caller, so LRU eviction ordered by the
download date and evicted the most-played tracks first. Touch in
resolve_offline_bytes, the one resolver every offline play goes through.

cc
2026-08-28 19:54:47 -06:00
vicrodh e0a9f60336 fix(scrobble): stamp started_at and time the threshold by audible position
The delayed scrobble used unix_now() when it fired (up to 240 s late on
a long track) and slept on wall-clock, so a track paused two minutes in
still scrobbled. Capture started_at on the track edge and wait on the
player's own position while it is playing: pause holds the scrobble, a
stop drops it, a newer track edge still self-cancels via the generation.
Same rule qbzd's scrobble_engine already applies.

cc
2026-08-28 19:54:47 -06:00
vicrodh 924af24e91 fix(reco): route Recommendations MusicBrainz lookups through the core client
recommendations_qt and external_reco_qt built private MusicBrainzClient
instances, which start enabled and ignore the musicbrainz_enabled
opt-out that integrations_qt applies to the core's client. Expose the
core's Arc<MusicBrainzClient> and borrow it instead, so a disabled
MusicBrainz sends zero requests from Discover > Recommendations. The tag
editor's three private clients stay: those are explicit manual lookups
the user triggers by hand.

cc
2026-08-28 19:54:47 -06:00
vicrodh 136f4d77ad merge: integrate local library Qt workflows
cx
2026-08-28 18:50:36 -06:00
vicrodh c7a3012955 feat(local-library): complete catalog and metadata workflows
Add bounded native library models, complete grouping rails and scan progress. Bring source-aware metadata editing, media information and collection offline preflight to Qt, and repair inherited Plex album artwork without overriding healthy tokens.

cx
2026-08-28 18:50:05 -06:00
vicrodh 353ed7fff8 test(windows): verify both URL protocols in MSI smoke
cx
2026-08-28 10:13:17 -06:00
vicrodh 351f9786a9 ci(storage): shorten preflight artifact retention
cx
2026-08-28 10:12:08 -06:00
vicrodh 05cc0c815f ci(windows): add fast MSI authoring preflight
cx
2026-08-28 09:44:25 -06:00
vicrodh 783dbb964e fix(windows): make MSI authoring valid on WiX 5
cx
2026-08-28 09:44:25 -06:00
vicrodh 6ad7bd05ce ci(cache): reuse qbzd builds across Linux packaging
cx
2026-08-28 09:44:25 -06:00
vicrodh d9fadfdb7c ci(cache): seed release caches from pre-release
cx
2026-08-28 09:44:25 -06:00
vicrodh 9d8af4b59d docs(ci): update qmllint parity note
cx
2026-08-28 09:44:25 -06:00
vicrodh bf7a071073 ci(packaging): add safe AUR and Gentoo dry runs
cx
2026-08-28 09:44:25 -06:00
vicrodh d987aaa798 fix(ci): preserve macdeployqt failures
cx
2026-08-28 09:44:25 -06:00
vicrodh aaf9573c57 fix(ci): harden Gentoo overlay publishing
cx
2026-08-28 09:44:25 -06:00
vicrodh ac506e4d6c fix(ci): harden AUR release publishing
cx
2026-08-28 09:44:25 -06:00
vicrodh ecd4fd05c3 fix(ci): refuse partial updater manifests
cx
2026-08-28 09:44:25 -06:00
vicrodh 3e3c5ffc2a fix(ci): allow PR retargeting from forks
cx
2026-08-28 09:44:25 -06:00
vicrodh 2a2c2fb2ea fix(ci): make Snap preflights safe and reproducible
cx
2026-08-28 09:44:25 -06:00
vicrodh dc7b1ba02b fix(ci): make macOS release failures authoritative
cx
2026-08-28 09:44:25 -06:00
vicrodh c79a8ab54f fix(ci): build requested release refs consistently
cx
2026-08-28 09:44:25 -06:00
vicrodh bf710fb7eb ci(qml): share qmllint gate across Linux and Windows
cx
2026-08-28 09:44:25 -06:00
vicrodh 9a0dbd60c2 fix(ci): use pre-OSMF WiX for Windows packaging
cx
2026-08-28 09:44:25 -06:00
vicrodh 0b7fa6da93 test(audio): honor platform support in direct-route assertions
cx
2026-08-28 09:44:25 -06:00