Commit Graph
100 Commits
Author SHA1 Message Date
vicrodh 4bb79fdd70 ci(updater): pass --no-default-features in sandboxed builds
Snap and Flathub builds now invoke
`npm run tauri build -- --no-bundle -- --no-default-features` so
cargo compiles without the `updater` feature, excluding
tauri-plugin-updater and tauri-plugin-process from the binary and
satisfying store policies that prohibit competing auto-updaters.

cc
2026-04-13 18:36:30 -06:00
vicrodh 031da1792d feat(updater): gate auto-updater behind Cargo feature updater
Makes tauri-plugin-updater and tauri-plugin-process optional
dependencies enabled by a default-on `updater` Cargo feature.
Sandboxed packaging targets (Flatpak, Snap) will build with
`--no-default-features` to exclude the plugins entirely,
satisfying store policies that prohibit competing auto-updaters.

Changes:
- Cargo.toml: add `updater` feature; plugins are now optional deps
- lib.rs: gate plugin registration on the feature
- updates/mod.rs: is_auto_update_eligible() returns false when
  the feature is off (catch-all for unusual build configs)
- capabilities/default.json: remove updater/process permissions
- build.rs: emit capabilities/auto-updater.json only when the
  feature is enabled; remove it otherwise. The file is gitignored
  since it is fully build-driven.

cc
2026-04-13 18:32:28 -06:00
vicrodh 0dd0313087 Merge branch 'feature/internal/auto-updater' into pre-release
Adds in-app auto-updater via tauri-plugin-updater (macOS + Linux
AppImage only; Flatpak/Snap/DEB/RPM keep their own update channels).
Includes signed release artifacts, updater manifest generation,
progress modal, and i18n coverage in all 5 locales.

cc
2026-04-13 15:22:25 -06:00
vicrodh 1777760aec chore(updater): set real minisign public key
Replaces PLACEHOLDER_PUBLIC_KEY with the real minisign public key
generated via 'tauri signer generate'. The corresponding private key
and password are stored locally; they must be added to GitHub Actions
secrets (TAURI_SIGNING_PRIVATE_KEY and TAURI_SIGNING_PRIVATE_KEY_PASSWORD)
before the first signed release.

Minisign public keys are intended to be public and embedded in the
binary; the updater uses this key to verify the signature of the
downloaded update bundle.

cc
2026-04-13 15:16:00 -06:00
vicrodh 347a8c1016 feat(updater): wire auto-update handlers in app and settings
Additively wires the in-app auto-update flow: on +page.svelte the
Update Available modal offers Download & Install when eligible, and
on SettingsView the Check for Updates result modal does the same.
Both show the UpdateProgressModal while the update runs, with
Cancel (pre-install) and Download manually fallback (on error).

No existing behavior is modified: nextTrackGuarded, volumeLocked,
repeat-one handling, player bar height variables, and the platform
helper all remain untouched.

cc
2026-04-13 15:00:28 -06:00
vicrodh 1372040990 feat(updater): extend update modals with auto-update option
Additive: adds optional autoUpdateEligible and onAutoUpdate props to
UpdateAvailableModal and UpdateCheckResultModal. When both are set,
the primary button becomes 'Download & Install' and 'Visit release
page' stays as a secondary option. Existing $t() translations are
preserved.

cc
2026-04-13 14:58:30 -06:00
vicrodh 10b5d95852 feat(updater): add performAutoUpdate and eligibility to store/service
Additive changes only: adds isAutoUpdateEligible() getter and
eligibility fetch in initUpdatesStore (keeps existing skipIfRemote
guards); adds performAutoUpdate() in updatesService for the
download/install/relaunch flow with cancellation support.

cc
2026-04-13 14:57:57 -06:00
vicrodh 46f8693f16 feat(updater): add UpdateProgressModal with i18n
New modal component that shows download progress, install and restart
states during an in-app auto-update. Uses a regular function (not
$derived with $t) for the status text to avoid Svelte Phantom CSS
Error (see ADR-001).

cc
2026-04-13 14:57:09 -06:00
vicrodh f664ce327d i18n(updater): add autoUpdate keys and download actions
Adds updates.autoUpdate.* keys (title, checking, downloading,
downloadingProgress, installing, restarting, failed) and
actions.downloadAndInstall / actions.downloadManually to all 5
locales (en, es, de, fr, pt).

cc
2026-04-13 14:56:39 -06:00
vicrodh a98d6d21bf chore(deps): add @tauri-apps/plugin-updater and plugin-process
cc
2026-04-13 14:54:04 -06:00
vicrodh 2f5a6aaddf ci(updater): sign artifacts and consolidate updater manifest
Adds TAURI_SIGNING_PRIVATE_KEY(_PASSWORD) env to release-linux,
release-linux-aarch64, and release-macos build steps, collects the
resulting .sig files into the release artifacts, and adds a new
release-updater-manifest workflow that waits for all platform sig
files to land on the release and assembles latest.json for the
updater endpoint.

Cherry-picked from #305 by @afonsojramos.

cc
2026-04-13 14:53:52 -06:00
vicrodh 96cfb93956 feat(updater): add tauri-plugin-updater backend integration
Adds tauri-plugin-updater and tauri-plugin-process crates, the updater
config block in tauri.conf.json (with PLACEHOLDER_PUBLIC_KEY to be
replaced before release), capabilities for updater/process, and the
v2_is_auto_update_eligible command that returns true on macOS and
Linux AppImage installs only.

Cherry-picked from #305 by @afonsojramos.

cc
2026-04-13 14:53:08 -06:00
vicrodh 6c66961ca2 chore(deps-dev): bump @sveltejs/kit from 2.57.0 to 2.57.1
Supersedes PR #306 (dependabot conflict with pre-release).
Includes security fixes for redirect validation and BODY_SIZE_LIMIT
enforcement on chunked requests.

cc
2026-04-13 14:08:04 -06:00
vicrodh 64a42fd156 fix(qbzd): use SetState.next_track for auto-advance + periodic state reports
When iOS controls via QConnect without sending a queue, the local
QbzCore queue is empty so auto-advance fails. Now we extract next_track
from each SetState command, store it in DaemonCore, and the orchestrator
uses it as a fallback when the local queue is empty.

Also adds a periodic state reporter (every 2s) so iOS knows the
renderer's actual state (playing/paused/stopped + position). Without
this iOS thinks the daemon is still playing after a track ends.

Bump version to 0.1.9.

cc
2026-04-10 00:42:02 -06:00
vicrodh 4112cc77bd fix(qbzd): materialize QConnect remote queue for auto-advance
When QConnect controls the daemon, the local QbzCore queue is empty so
the orchestrator's auto-advance fails. Now on QueueUpdated, fetch all
tracks via get_tracks_batch and populate the local queue, mirroring
desktop's materialize_remote_queue. Bump version to 0.1.8.

cc
2026-04-09 23:22:20 -06:00
vicrodh 5182197938 fix(qbzd): prevent playback restart from server state echo
Skip redundant resume() when already playing, and ignore seek-to-zero
from server refresh echoes after renderer join. Matches desktop's
should_force_remote_track_restart guard logic.

cc
2026-04-09 23:18:26 -06:00
vicrodh fc83ad6cc8 chore(qbzd): bump version to 0.1.7
cc
2026-04-09 22:49:48 -06:00
vicrodh 3df007fe9f fix(qbzd): download and play track on QConnect renderer SetState
When receiving RendererCommandApplied::SetState with a current_track,
actually download the track audio and feed it to the player before
applying the playing state. Previously only called core.resume() which
did nothing since no audio was loaded.

Matches desktop's ensure_remote_track_loaded pattern from
qconnect_service.rs. Also adds >2s threshold for seek to avoid
redundant seeks from server echo.

cc
2026-04-09 22:48:27 -06:00
vicrodh 79a86278f2 fix(qbzd): complete device_info with device_uuid + capabilities
Missing fields that the Qobuz server needs to register a renderer:
- device_uuid: persistent UUID (generated once, saved to disk)
- capabilities: min/max audio quality + volume_remote_control
- serial_number: null (same as desktop)

Without device_uuid, the server can't identify the renderer and
won't send SESSION_STATE.

Copied from desktop's default_qconnect_device_info_with_name.

cc
2026-04-09 22:35:03 -06:00
vicrodh ffa59b339a fix(qbzd): rewrite QConnect to exact desktop pattern
Complete rewrite of qconnect.rs following desktop's
QconnectServiceState::connect() line by line:

1. Subscribe to transport events BEFORE bootstrap (was after)
2. Event loop checks InboundQueueServerEvent for SESSION_STATE
   (was checking SessionManagementEvent via sink channel)
3. Forward events to handle_transport_event AFTER inspection
4. Bootstrap called AFTER event loop starts
5. Re-bootstrap on reconnect (Subscribed + has_disconnected)

The key bug: event loop was subscribing AFTER bootstrap, missing
the SESSION_STATE response. Desktop subscribes at line 1238,
bootstraps at line 1405.

cc
2026-04-09 18:37:15 -06:00
vicrodh bea94871eb fix(qbzd): immediate renderer join in bootstrap, don't wait for SESSION_STATE
The Qobuz server doesn't always send SESSION_STATE after a controller
JoinSession (only on topology changes). This left the daemon invisible
as a renderer because deferred_renderer_join never triggered.

Now: renderer join happens immediately in bootstrap after a 1s delay,
using null session_uuid. The server should assign one. If a
SESSION_STATE arrives later via the sink channel, a second join
is attempted with the real uuid.

cc
2026-04-09 18:33:13 -06:00
vicrodh 186e79d225 fix(qbzd): QConnect renderer join via SessionManagementEvent sink
SESSION_STATE comes as QconnectAppEvent::SessionManagementEvent
through the sink, NOT as a transport InboundQueueServerEvent.
The event loop was looking in the wrong place.

Now: sink receives SessionManagementEvent, extracts session_uuid,
sends it via mpsc channel to the event loop which does the
deferred_renderer_join. This matches the desktop pattern where
TauriQconnectEventSink processes session management events.

cc
2026-04-09 18:08:34 -06:00
vicrodh fb10b46ac7 fix(qbzd): early return on remote play, zero local side effects
When target is qbzd, playTrack() sends play-track to daemon and
returns immediately. No local stop (was killing daemon audio),
no cmdStop (was sending HTTP stop to daemon), no metadata update,
no scrobbling, no QConnect handoff.

The daemon handles stop-before-play internally in its play_track
endpoint. The desktop sends ONE command and exits. Zero leakage.

cc
2026-04-09 14:49:26 -06:00
vicrodh 82ea7517c8 chore(qbzd): own version 0.1.6, decoupled from workspace
cc
2026-04-09 14:45:20 -06:00
vicrodh c2e8285dfa fix(qbzd): prevent orchestrator race on play-track + QConnect debug logging
play-track/play-album set skip_auto_advance flag before stop() to
prevent the orchestrator from auto-advancing during the download
window. This fixes the 'track starts then immediately stops' bug.

Added QConnect event type logging to debug why SESSION_STATE never
triggers the deferred renderer join.

cc
2026-04-09 14:30:32 -06:00
vicrodh ca4480239b feat(qbzd): guard all stores/services for remote mode override
When controlling a qbzd daemon, QBZ Desktop becomes a pure remote
control. 119 invoke() call sites across 16 files now check
skipIfRemote() before executing local operations.

Local-only (skipped entirely): castStore, offlineStore,
offlineCacheState, updatesStore, titleBarStore, qobuzLegalStore,
sessionService.

Daemon-routable (guarded, daemon API routing in follow-up):
favoritesStore, albumFavoritesStore, artistFavoritesStore,
artistBlacklistStore, playlistFoldersStore, playbackPreferencesStore,
playbackContextStore.

playbackService: scrobbling, MPRIS metadata, and notifications
all skipped (daemon handles its own).

cc
2026-04-09 14:20:09 -06:00
vicrodh b49fce7662 feat(qbzd): guard all stores/services for remote mode override
cc
2026-04-09 14:19:25 -06:00
vicrodh 98286c6123 fix(qbzd): skip QConnect handoff when controlling daemon via HTTP
When playbackTarget is 'qbzd', the play-track command goes directly
to the daemon HTTP API. Previously, it also went through
handoffPlayTrackToRemoteRenderer (QConnect) which sent play to ALL
connected renderers, causing audio on both local desktop AND remote
daemon simultaneously.

Now: qbzd mode = HTTP API only. QConnect handoff only runs in
local mode. Local player stopped explicitly via invoke before
sending to daemon.

cc
2026-04-09 14:04:38 -06:00
vicrodh 39142aad3b feat(qbzd): proper playback orchestrator with gapless + repeat modes
Replaces naive auto-advance polling with full orchestrator matching
desktop behavior:

Gapless: when player signals gapless_ready (5s before track end),
downloads next track and calls play_next() for seamless transition.
Handles repeat-one (queue same track), defensive same-track check,
and upcoming queue peek (same as desktop's setGaplessGetNextTrackId).

Auto-advance: on track end, advances queue respecting repeat mode.
Repeat-one replays same track. Repeat-all wraps queue. Off stops
at queue end.

Shared download_track() helper used by play-track, play-album, and
orchestrator to avoid code duplication.

cc
2026-04-09 13:51:23 -06:00
vicrodh 30bc325f68 feat(qbzd): auto-advance queue + play-album endpoint
Auto-advance: background task monitors when track ends and
automatically downloads and plays the next track in the queue.
Polls every 500ms. Queue exhaustion logs and stops.

Play-album: POST /api/playback/play-album sets the queue with
all album tracks and starts playing from the specified index.

cc
2026-04-09 13:30:15 -06:00
vicrodh 3a01330b02 fix(ui): banner layout uses CSS variable, fix i18n key, responsive height
Banner inside now-playing-bar grows it to 128px. CSS variable
--player-bar-height drives content-area/main-content calc() so
layout adjusts automatically. No more overlap.

Fix disconnect button text: actions.disconnect -> settings.integrations.disconnect

cc
2026-04-09 13:26:15 -06:00
vicrodh 72254ac44e fix(qbzd): remove token requirement from remote API client
LAN-only mode means no token auth. The empty string token ('') was
failing the truthy check in requireRemote(), causing 'Not in remote
mode' error on every command after connecting.

Now: token header only sent when token is non-empty. requireRemote
only checks type === 'qbzd' and baseUrl presence.

This was THE bug preventing QBZ Desktop from controlling the daemon.

cc
2026-04-09 13:11:26 -06:00
vicrodh 6cf396aa8a fix(qbzd): QConnect bootstrap clears pending between commands
The second command (AskForQueueState) was failing because the
first (JoinSession) was still pending. Now clears pending slot
between commands with 500ms delay, matching desktop behavior.

Also fixed state_handle() Arc lifetime issues.

cc
2026-04-09 12:53:29 -06:00
vicrodh e9fe6b6245 fix(qbzd): QConnect form-encoded createToken + mDNS hostname + response parsing
3 fixes:
1. qws/createToken needs form body (jwt=jwt_qws, user_auth_token_needed,
   strong_auth_needed) not empty body. Was getting 400 Bad Request.
2. mDNS hostname must end with '.local.' for service registration.
3. Response parsing: jwt_qws.endpoint + jwt_qws.jwt, not tokens array.

All 3 patterns copied from working desktop implementation in
qconnect_service.rs.

cc
2026-04-09 12:29:07 -06:00
vicrodh 457c628318 feat(qbzd): implement play-track endpoint (download + play)
POST /api/playback/play-track accepts track_id + quality, downloads
the audio stream from Qobuz, and feeds it to the Player.

This was the missing piece — without this, the daemon could search
and manage queue but couldn't actually play anything.

cc
2026-04-09 12:05:16 -06:00
vicrodh ca9ef961cb fix(qbzd): full QConnect protocol + createToken Content-Length fix
QConnect now implements the full registration protocol:
1. Connect WebSocket
2. Bootstrap: CtrlSrvrJoinSession + AskForQueueState
3. Event loop listening for SESSION_STATE
4. Deferred renderer join with device_info
5. State/volume/quality reports

Fix 411 Length Required on qws/createToken (missing Content-Length: 0).
Fix renderer command handler to execute play/pause/stop/seek/volume.

cc
2026-04-09 12:00:18 -06:00
vicrodh 264302fd8e feat(qbzd): implement all wizard sections (cache, integrations, qconnect)
Cache: interactive RAM detection, L1/L2 size, prefetch config.
Saves to qbzd.toml via save_default_config.

Integrations: ListenBrainz token input, saves directly to V2
cache database. MusicBrainz enable/disable toggle.

QConnect: enable/disable + device name. Saves to qbzd.toml.

All sections read current values as defaults and persist changes.
No more placeholders.

cc
2026-04-09 11:39:22 -06:00
vicrodh c63799309f fix(ci): use native ARM64 runner instead of QEMU for aarch64 build
QEMU emulation crashes compiling ring's C code (SIGSEGV in cc).
Switch to ubuntu-24.04-arm native runner — faster and no emulation
issues. Build time drops from ~40min to ~5min.

cc
2026-04-09 11:19:52 -06:00
vicrodh 7a9f8407a8 fix(qbzd): file-based token fallback when keyring unavailable
On headless systems without gnome-keyring/kwallet, the keyring
fails with ServiceUnknown. Now saves token to ~/.local/share/qbz/.oauth-token
(mode 0600) as fallback. Load also checks file after keyring.

This fixes the 'login works but credentials lost on restart' bug
on headless machines.

cc
2026-04-09 10:57:30 -06:00
vicrodh fd1a4fade1 fix(qbzd): route ALL playback and queue calls through commandRouter
playbackService.ts: v2_play_track and v2_stop_playback now route
to daemon HTTP API when in remote mode.

9 views: bulk queue operations (add_tracks_to_queue, add_tracks_to_queue_next)
now use cmdAddTracksToQueue/cmdAddTracksToQueueNext instead of
direct invoke(). 14 call sites total.

This was the root cause of 'connects but nothing plays' — the
play/queue commands were always going to local Tauri, never to
the remote daemon.

cc
2026-04-09 10:51:20 -06:00
vicrodh e4b6024ade fix(ui): remote indicator sits above player bar, not inside it
Moves the 'Controlling: X' banner from inside the now-playing-bar
to a fixed position above it (bottom: 104px). The player bar
keeps its 104px height unchanged. Main content area shrinks
slightly when the banner is visible.

cc
2026-04-09 09:45:17 -06:00
vicrodh a9282e2091 feat(qbzd): manual daemon connect + persistence + headless login
CastPicker: manual IP:port input for connecting to daemons when
mDNS/avahi is unavailable. Saves recent connections to localStorage
(max 5). One-click reconnect from saved list. Remove button on hover.

Layout: manual input field at top, saved daemons as 'Recent',
mDNS discovered daemons below with scanning indicator.

Login: binds to 0.0.0.0 with auto-detected LAN IP so the OAuth
callback URL works from any browser on the network. 5 min timeout
for remote copy-paste workflow.

i18n: recentDaemons, discoveredDaemons in all 5 locales.

cc
2026-04-09 09:28:01 -06:00
vicrodh c21e87e1c0 fix(qbzd): headless-compatible login with LAN IP detection
qbzd login now works on remote/headless machines:
- Binds callback server to 0.0.0.0 (not 127.0.0.1)
- Detects LAN IP via UDP socket probe for redirect URL
- Always prints URL prominently for copy-paste to any browser
- Browser on same LAN redirects back to daemon's IP:port
- Timeout increased to 300s for remote copy-paste workflow
- open::that still tried but failure is silent (headless)

cc
2026-04-09 09:02:06 -06:00
vicrodh aaa183ce35 feat(ui): add Select All checkbox to multi-select mode in all track views
cc
2026-04-09 08:56:36 -06:00
vicrodh 2802527912 chore(deps): update jsdom 29.0.2, notify-rust 4.14.0
Also closed PR #298 (md-5 0.11) — breaking API change across 9
call sites for zero functionality gain.

cc
2026-04-09 08:38:19 -06:00
Victor RH b196a299dc Merge pull request #300 from vicrodh/dependabot/cargo/src-tauri/pre-release/tauri-plugin-dialog-2.7.0 2026-04-09 08:20:17 -06:00
Victor RH 1bc3eaf759 Merge pull request #299 from vicrodh/dependabot/cargo/src-tauri/pre-release/rodio-0.22.2 2026-04-09 08:20:13 -06:00
Victor RH c1cb47779d Merge pull request #297 from vicrodh/dependabot/npm_and_yarn/pre-release/tauri-apps/plugin-dialog-2.7.0 2026-04-09 08:20:10 -06:00
Victor RH 5c21e7b0d5 Merge pull request #296 from vicrodh/dependabot/npm_and_yarn/pre-release/vite-8.0.8 2026-04-09 08:20:06 -06:00
Victor RH 15c926ea6f Merge pull request #295 from vicrodh/dependabot/cargo/src-tauri/pre-release/tauri-plugin-deep-link-2.4.8 2026-04-09 08:20:02 -06:00
Victor RH 8af186797e Merge pull request #294 from vicrodh/dependabot/npm_and_yarn/pre-release/sveltejs/kit-2.57.0 2026-04-09 08:19:58 -06:00
Victor RH 2374020945 Merge pull request #292 from vicrodh/dependabot/npm_and_yarn/pre-release/svelte-5.55.2 2026-04-09 08:19:53 -06:00
vicrodh dd322a1804 fix(ci): use QEMU native build for aarch64 instead of cross-compile
Ubuntu runners' apt multi-arch is broken for arm64 cross-deps
(security.ubuntu.com 404s for arm64 packages, DEB822 format
sources not handled by sed).

Switch to uraimo/run-on-arch-action: builds natively inside a
Debian Bookworm aarch64 container via QEMU. Slower (~15 min)
but reliable. Binary is native aarch64 with Bookworm's glibc 2.36.

cc
2026-04-09 07:28:48 -06:00
vicrodh 1cba3ec18b fix(ci): force all apt sources to amd64, arm64 from ports only
The sed was only modifying /etc/apt/sources.list but security.ubuntu.com
and other sources in sources.list.d/ were also trying to fetch arm64
packages from repos that don't have them (404).

Now: force [arch=amd64] on ALL .list files, arm64 exclusively from
ports.ubuntu.com.

cc
2026-04-09 07:16:37 -06:00
vicrodh 22a34b4f3e fix(ci): target glibc 2.35 instead of 2.31
glibc 2.31 is too old for system shared libs (libasound, libdbus)
on Ubuntu 22.04 runners which have glibc 2.35. The linker rejects
symbols from libs compiled against newer glibc.

glibc 2.35 covers: Raspberry Pi OS Bookworm (2.36), Ubuntu 22.04+,
Armbian Bookworm, DietPi Bookworm. Bullseye (2.31) is EOL June 2026.

cc
2026-04-09 07:08:22 -06:00
vicrodh 7a522623cf fix(ci): add libdbus-1-dev, fix arm64 apt sources, add strip
x86_64: add libdbus-1-dev for keyring D-Bus dependency
aarch64: fix apt sources with jammy-updates, proper arch restrictions
Both: strip binaries for smaller release size (~23 MB vs 30 MB)

cc
2026-04-09 06:57:12 -06:00
vicrodh c3ce3c6667 fix(ci): drop openssl dependency, use rustls-only for all crates
reqwest default features included native-tls which pulled in
openssl-sys. This broke zigbuild cross-compilation because zig's
C compiler doesn't support the glibc version suffix for openssl
header expansion.

Fix: default-features = false on reqwest in workspace and
qbz-integrations (which had its own reqwest dep with defaults).
Now the entire daemon tree uses rustls-tls exclusively.

cc
2026-04-09 06:48:37 -06:00
vicrodh d042d21622 fix(ci): add libssl-dev + pkg-config to qbzd build
openssl-sys build requires OpenSSL headers even when rustls is the
primary TLS backend (native-tls comes as transitive dependency).

cc
2026-04-09 06:37:11 -06:00
vicrodh 8cee1c60be ci(qbzd): fix workflow paths, add cache, track Cargo.lock
Fixes:
- Rust cache via Swatinem/rust-cache (workspaces: crates -> target)
- Track crates/Cargo.lock for reproducible builds
- Bundle service files in release tarball
- Add permissions: contents: write for release job
- generate_release_notes for auto changelog

Local release build verified: 30 MB (23 MB stripped), x86_64.

cc
2026-04-09 06:22:17 -06:00
vicrodh 586d27bf43 feat(qbzd): integrate daemon discovery into CastPicker
QBZ Daemon tab appears first in the device picker alongside
Chromecast and DLNA. Discovered daemons show name + host:port.

Clicking a daemon:
1. Pings to verify it's reachable
2. Connects via playbackTargetStore
3. Starts SSE event subscription
4. Closes picker

Connected daemon shows in the connected banner with disconnect.
Disconnecting returns to local playback mode.

i18n: noQbzdFound + noQbzdFoundHint in all 5 locales.

cc
2026-04-09 06:08:08 -06:00
vicrodh eade143342 feat(qbzd): TUI setup wizard with ratatui (Phase 1.5)
Interactive menu-based wizard accessible via 'qbzd setup':
- Audio Backend & Device: lists backends, devices, saves selection
- Cache & Resources: instructions for qbzd.toml config
- Integrations: ListenBrainz token setup instructions
- QConnect: config instructions

Full ratatui TUI with keyboard navigation (up/down/enter/q).
Individual sections runnable via 'qbzd setup --section audio'.

Audio section is fully functional: enumerates backends and devices,
saves selection to audio_settings.db.

cc
2026-04-09 05:44:54 -06:00
vicrodh 632b134952 feat(qbzd): full metadata scan using MetadataExtractor
Library scan now extracts metadata (title, artist, album, duration,
sample rate, bit depth) from audio files using qbz-library's
MetadataExtractor. Tracks are inserted into the library database
with full metadata.

MetadataExtractor was already in qbz-library crate — no migration
needed.

cc
2026-04-09 05:42:27 -06:00
vicrodh 90769c0537 ci: add qbzd build workflow (x86_64 + aarch64, glibc 2.31)
Uses cargo-zigbuild to target glibc 2.31 for maximum compatibility:
- Raspberry Pi OS Bullseye and newer
- DietPi, Armbian, Ubuntu 22.04+

Triggered by qbzd-v* tags or manual dispatch.
Produces two binaries: qbzd-linux-x86_64, qbzd-linux-aarch64.
Creates pre-release on tag push.

cc
2026-04-09 05:40:41 -06:00
vicrodh e5539132da feat(qbzd): QConnect headless integration
HeadlessQconnectSink routes QConnect events to the daemon event bus.
Fetches QWS JWT credentials from Qobuz API, connects via WebSocket.

The daemon now registers as a Qobuz Connect renderer — controllable
from the Qobuz mobile app and web player. QConnect starts
automatically after login if enabled in config.

cc
2026-04-09 05:39:34 -06:00
vicrodh b3e5be39eb feat(qbzd): MPRIS metadata from CoreEvent::TrackStarted
Spawns a dedicated event bus listener that updates MPRIS metadata
(title, artist, album, duration) when a new track starts playing.
Clears metadata on logout.

MPRIS now fully functional: media keys (play/pause/next/prev) +
metadata display (track info in system media widget).

cc
2026-04-09 05:35:52 -06:00
vicrodh a5efd6ae19 feat(qbzd): setup subcommand placeholder + config save helper
CLI now shows all 5 subcommands: login, status, token, setup, help.
Setup placeholder ready for TUI wizard implementation (ratatui).
Config save helper for persisting changes to qbzd.toml.

cc
2026-04-09 05:30:12 -06:00
vicrodh e0301b6a29 feat(qbzd): MPRIS playback updates + status subcommand
Playback loop now updates MPRIS state (playing/paused/position)
on every emit cycle so media controls always reflect current state.

qbzd status: queries running daemon via HTTP and prints status.
Shows logged_in state, port, and daemon info.

cc
2026-04-09 05:26:37 -06:00
vicrodh f9d6e551e2 feat(qbzd): LAN CORS, headless MPRIS, security model
LAN-only CORS: allows any origin on local network for qbz-control PWA.

MPRIS via souvlaki (D-Bus, no window): play/pause/toggle/next/prev/stop
media keys work out of the box on any Linux desktop or headless system
with D-Bus. Metadata and playback state updates available via helpers.

Security model finalized:
- LAN: open access, no auth needed (private IP check)
- WAN: completely blocked (403 for non-private IPs)
- Remote: QConnect only (cloud, Qobuz-managed auth)

cc
2026-04-09 05:04:09 -06:00
vicrodh 892560dff4 feat(qbzd): LAN-only access restriction, no token auth
Replaces token-based authentication with LAN-only IP restriction.
Requests from non-private IPs (not 10.x, 172.16.x, 192.168.x,
127.x, link-local, ULA) are rejected with 403.

Security model: LAN = trusted, WAN = blocked.
Remote control outside LAN is exclusively via QConnect (cloud).

cc
2026-04-09 05:00:40 -06:00
vicrodh aacdeabe07 feat(qbzd): login subcommand, library scan, service files (68 endpoints)
qbzd login: interactive OAuth via system browser. Opens browser,
captures callback, exchanges code, saves token to keyring. Same
flow as desktop app. Daemon auto-logins on next start.

Library scan endpoint (POST /api/library/scan): discovers audio
files in registered folders. Full metadata extraction deferred
until MetadataExtractor moves to qbz-library crate.

Service files: systemd, OpenRC, runit + example qbzd.toml config.

cc
2026-04-09 04:52:58 -06:00
vicrodh 42a8ff5665 feat(qbzd): library management + integrations API (67 endpoints)
Library endpoints for standalone operation:
- GET /api/library/albums, artists, album tracks, search, stats
- POST/DELETE /api/library/folders (add/remove scan directories)

Integrations endpoints:
- GET/POST/DELETE /api/integrations/listenbrainz (status, connect, disconnect)
- GET /api/integrations/lastfm (status, setup deferred to TUI wizard)

ListenBrainz connects by writing token directly to the V2 cache
database (same format as desktop app). Last.fm requires OAuth
flow which will be implemented in the TUI wizard.

Total: 67 HTTP endpoints. The daemon is now fully operable
without the desktop app for all core functionality.

cc
2026-04-09 04:46:12 -06:00
vicrodh b524d62318 feat(qbzd): API tiers 2-3 — discover, playlists, favorites, labels, system (55 endpoints)
Tier 2 - Browsing & Management:
- Discover: index, playlists, featured, genres (4 endpoints)
- Favorites: get, add, remove (3)
- Playlists: CRUD + tracks + search (8)
- Extended catalog: artist page, similar, labels, tags (6)

Tier 3 - Server Management:
- System resources: RAM, cache stats (1)
- Cache clear (1)

Total: 55 HTTP endpoints covering playback, queue, search, catalog,
discover, favorites, playlists, audio settings, labels, and system.

cc
2026-04-09 04:41:49 -06:00
vicrodh 8c947c02b4 feat(qbzd): audio settings API + remote settings routing (Phase 5e)
Daemon: 5 new audio settings endpoints:
- GET /api/audio/settings (full settings)
- PATCH /api/audio/settings (partial update)
- GET /api/audio/backends (available backends)
- GET /api/audio/devices?backend= (devices for backend)
- GET /api/audio/hardware-status (current hw state)

Frontend: commandRouter gains audio settings commands that route
to daemon API when in remote mode, local invoke when local.

Total daemon endpoints: 32.

cc
2026-04-09 04:35:49 -06:00
vicrodh 8fc6111782 feat(qbzd): mDNS discovery + registration for LAN pairing
Daemon side: registers _qbz._tcp.local mDNS service on startup
so desktop apps can discover it on the LAN. Uses hostname as
service name (configurable via qbzd.toml).

Desktop side: new qbzd_discovery.rs module with Tauri commands:
- v2_qbzd_start_discovery: browse _qbz._tcp services
- v2_qbzd_stop_discovery: stop browsing
- v2_qbzd_get_devices: list discovered daemons

Uses same mdns-sd crate already used for AirPlay discovery.

cc
2026-04-09 04:30:08 -06:00
vicrodh 8dac185261 feat(qbzd): remote mode indicator in NowPlayingBar
Accent-colored banner shows 'Controlling: {device_name}' with a
disconnect button when the app is targeting a remote qbzd daemon.
Hidden in local mode.

i18n: player.controllingRemote in all 5 locales.

cc
2026-04-09 04:22:00 -06:00
vicrodh e3c1e471c6 feat(qbzd): bifurcate playback and queue stores for remote mode
New commandRouter.ts provides a single bifurcation point for all
playback and queue operations. When playbackTarget is 'qbzd', commands
route to the daemon HTTP API. When 'local', they use Tauri invoke().

Stores updated:
- playerStore.ts: pause, resume, stop, seek, volume, play_track (6 sites)
- queueStore.ts: shuffle, repeat, add, add-next, set, clear (8 sites)

No behavioral change in local mode — invoke() calls are identical.

cc
2026-04-09 04:16:28 -06:00
vicrodh 0d94f21479 feat(qbzd): add remote playback target store and API services
New files for desktop controller mode (Phase 5):
- playbackTargetStore.ts: local/remote mode switching
- remoteApi.ts: HTTP client for daemon API (GET/POST + error handling)
- remoteEvents.ts: SSE client for real-time daemon events

cc
2026-04-09 04:09:24 -06:00
vicrodh 3b8555992e feat(qbzd): HTTP API tier 1 - playback, queue, search, catalog (Phase 4)
27 endpoints covering core remote control functionality:

System (4): ping, info, status, SSE events
Playback (8): get state, play, pause, stop, next, prev, seek, volume
Queue (10): get, set, add, add-next, play-index, remove, move, clear,
  shuffle, repeat
Search (1): unified search (all/albums/tracks/artists via type param)
Catalog (4): album detail, artist detail, track detail, batch tracks

All handlers call QbzCore methods directly (no Tauri dependency).
with_daemon! macro reduces route handler boilerplate.

cc
2026-04-09 03:43:54 -06:00
vicrodh ac17787264 feat(qbzd): unified event bus + SSE endpoint (Phase 3)
Playback event loop polls QbzCore player state with adaptive timing
(250ms playing, 1s paused, 5s idle) and broadcasts PlaybackSnapshot
events to the event bus.

New SSE endpoint at /api/events streams all DaemonEvents to clients.
Broadcast channel bounded to 256 events with lagged client handling.

cc
2026-04-09 03:38:06 -06:00
vicrodh 6ee123c2a4 feat(qbzd): add session lifecycle port (Phase 2)
New session.rs module ports activate_session from Tauri to headless:
- Creates per-user directories (same layout as desktop app)
- Persists last_user_id marker for session restore
- Emits RuntimeEvent::Ready via event bus
- Skips desktop-only stores (tray, visualizer, remote control)

Session activation wired into daemon startup after auto-login.
Audio settings reload deferred until qbz-player crate exposes
reload_settings (currently on legacy player only).

cc
2026-04-09 03:23:56 -06:00
vicrodh 2916dee42c feat(qbzd): wire QbzCore, Player, AudioCache, keyring (Phase 1)
DaemonCore now initializes the real QbzCore with DaemonAdapter,
creates Player with audio settings from shared database, and
attempts auto-login from saved OAuth token in system keyring.

Working features:
- QbzCore init (bundle token extraction from Qobuz web player)
- Player audio thread (ready for playback commands)
- AudioCache with auto-detected size from RAM
- Shared audio settings with desktop app (same SQLite DB)
- Keyring credential lookup (same service as desktop)
- HTTP endpoints: /api/ping, /api/info, /api/status
- Graceful shutdown (SIGTERM/SIGINT -> stop player -> exit)

cc
2026-04-08 21:29:28 -06:00
vicrodh b0ed467664 feat(qbzd): scaffold headless daemon binary (Phase 0)
New workspace member crates/qbzd/ with:
- CLI interface (clap): port, bind, data-dir, config, token, log-level
- Config system (TOML): server, audio, cache, data, mpris, qconnect, mdns
- Resource detection (sysinfo): auto-size cache from available RAM
- DaemonAdapter: FrontendAdapter impl routing CoreEvents to broadcast channel
- DaemonCore: state container replacing Tauri's app.state
- Minimal HTTP server (Axum): /api/ping + /api/info
- Graceful shutdown on SIGTERM/SIGINT

Zero Tauri dependency. Desktop app unaffected.

cc
2026-04-08 21:18:01 -06:00
vicrodh 242b27bb1b refactor: clean up commands_v2/mod.rs
cc
2026-04-08 19:10:23 -06:00
vicrodh 77f12075da refactor: extract commands_v2/diagnostics.rs
cc
2026-04-08 18:07:54 -06:00
vicrodh 8fa11a4317 refactor: extract commands_v2/discovery.rs
cc
2026-04-08 18:05:50 -06:00
vicrodh b0ae328964 refactor: extract commands_v2/image_cache.rs
cc
2026-04-08 18:02:05 -06:00
vicrodh 709c399e31 refactor: extract commands_v2/session.rs
cc
2026-04-08 17:59:33 -06:00
vicrodh 6f1a1c8a22 refactor: extract commands_v2/settings.rs
cc
2026-04-08 17:55:33 -06:00
vicrodh 93154690e3 refactor: extract commands_v2/auth.rs
cc
2026-04-08 17:49:16 -06:00
vicrodh 45ab6c34b6 refactor: extract commands_v2/catalog.rs
cc
2026-04-08 17:46:59 -06:00
vicrodh 5f4cc58d99 refactor: extract commands_v2/playlists.rs
cc
2026-04-08 15:26:06 -06:00
vicrodh c746303f2b refactor: extract commands_v2/audio.rs
cc
2026-04-08 15:23:48 -06:00
vicrodh 98f17019d4 refactor: extract commands_v2/favorites.rs
cc
2026-04-08 15:18:37 -06:00
vicrodh 0303e40f32 refactor: extract commands_v2/search.rs
cc
2026-04-08 15:16:04 -06:00
vicrodh 6c6199a76d refactor: extract commands_v2/queue.rs
cc
2026-04-08 15:11:43 -06:00
vicrodh 02bae5b601 refactor: extract commands_v2/runtime.rs
cc
2026-04-08 15:01:25 -06:00
vicrodh 555139a6e2 refactor: extract commands_v2/playback.rs
cc
2026-04-08 14:44:55 -06:00
vicrodh f4c874fdac refactor: extract commands_v2/link_resolver.rs
cc
2026-04-08 14:30:05 -06:00
vicrodh d667c3e13c refactor: extract commands_v2/integrations.rs
cc
2026-04-08 13:47:46 -06:00
vicrodh 82e1b337a1 refactor: extract commands_v2/helpers.rs
cc
2026-04-08 13:34:51 -06:00