Commit Graph
12 Commits
Author SHA1 Message Date
vicrodh 3477edfb76 refactor: share the image cache and credential store via crates
Extracts two self-contained, framework-agnostic modules out of
src-tauri so they can be reused by other clients without duplicating
logic:

- ImageCacheService / ImageCacheStats move into the qbz-cache crate
  (LRU disk cache, MD5-keyed, SQLite index).
- The credential store (keyring + AES-256-GCM encrypted file for Qobuz
  credentials and the OAuth token) moves into a new qbz-credentials
  crate.

src-tauri/image_cache.rs and src-tauri/credentials/mod.rs become thin
re-exports, keeping their Tauri managed-state wrappers. The Tauri app
is behavior-identical; both modules were already self-contained (no
Tauri and no crate:: references).

cc
2026-05-18 14:11:05 -06:00
vicrodh 4f008cd219 refactor(qbz-app): scaffold app crate 2026-05-14 11:40:19 -06:00
vicrodh 1743c9e5ec feat(secrets): add qbz-secrets crate for device-bound secret wrapping
Foundation for the offline-cache hardening story. The offline cache
needs to persist per-track AES content keys to disk, but storing them
plaintext would make copying the SQLite DB enough to decrypt the
cached audio on another machine.

qbz-secrets provides a uniform wrap/unwrap API backed by:

1. **OS keyring** (preferred): libsecret/gnome-keyring on Linux,
   Keychain on macOS, DPAPI on Windows. A 256-bit master AES key
   lives inside the OS secure store; all wraps go through it. This
   is the gold-standard device binding used by commercial music
   apps (Spotify, Apple Music, Tidal) for the same class of data.

2. **HKDF fallback**: when the OS keyring is not reachable (headless
   setups — Raspberry Pi, Docker, the future qbzd daemon on a
   server), the master key is derived on demand via HKDF-SHA256
   over service_name + machine_id + a persistent per-install UUID.
   Weaker than keyring (reversible by anyone with filesystem access
   to both sources) but still device-bound against cross-machine
   copy attacks, and lets daemon/headless variants work at all.

Every wrap uses AES-256-GCM with a fresh random 12-byte nonce. The
on-disk envelope carries a version byte + backend marker + nonce +
ciphertext+tag, so (a) we detect tampering via GCM, (b) we refuse to
silently unwrap a blob produced by a backend we can't reach, and (c)
we have a forward-compat path for future key rotation.

4 unit tests cover: small-secret round-trip, 16-byte content-key
round-trip (the exact CMAF use case), GCM tamper detection, and
nonce-randomness (two wraps of the same input differ).

This crate intentionally has zero QBZ dependencies — it's a generic
primitive. Next commits wire it into the offline cache schema + the
DB migration.

cc
2026-04-17 00:53:01 -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 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 794dd9f24c feat(cmaf): add qbz-cmaf crate with crypto module
HKDF-SHA256 session key derivation, AES-128-CBC content key unwrap,
AES-128-CTR frame decryption, and MD5 request signing for Qobuz
CMAF segmented streaming.

cc
2026-04-06 13:10:00 -06:00
vicrodh 373220c379 chore(qconnect): register workspace crates and tauri adapter dependency
-cx
2026-02-21 12:17:30 -06:00
vicrodh 63994c0a20 refactor(cast): extract qbz-cast crate
Extract casting functionality (Chromecast, AirPlay, DLNA) to standalone
qbz-cast crate. This crate is Tauri-agnostic and can be used by TUI/CLI
clients.

- Move device connection, discovery, threading, and media server to crate
- Keep only Tauri commands in src-tauri/src/cast/
- Thin re-exports in src-tauri for backwards compatibility
- Remove duplicate dependencies from src-tauri Cargo.toml
2026-02-15 14:51:06 -06:00
vicrodh d6e4b3349b feat(qbz-library): extract local library management to standalone crate
Create qbz-library crate with:
- LibraryScanner: directory scanning for audio files
- MetadataExtractor: audio metadata extraction via lofty
- LibraryDatabase: SQLite persistence with WAL mode
- CueParser: CUE sheet parsing for single-file albums
- Thumbnails: artwork extraction and thumbnail generation
- Tag sidecar: album metadata override files

src-tauri re-exports from qbz-library for backwards compatibility.
Enables TUI/headless clients to manage local libraries without Tauri.
2026-02-15 12:12:52 -06:00
vicrodh 384459d4d7 feat(qbz-cache): extract audio caching to standalone crate
Create qbz-cache crate with:
- AudioCache: L1 in-memory LRU cache (~300MB)
- PlaybackCache: L2 disk-based cache (~500MB)
- LRU eviction with spillover from L1 to L2

src-tauri now re-exports from qbz-cache for backwards compatibility.
This enables TUI/headless clients to use caching without Tauri.
2026-02-15 11:27:38 -06:00
vicrodh bd30e7cf24 feat(qbz-integrations): create third-party integrations crate
New crate for Last.fm, ListenBrainz, and MusicBrainz integrations.
Designed to work WITHOUT Tauri for TUI/headless clients.

Modules:
- lastfm: Scrobbling and now-playing via Cloudflare Workers proxy
- listenbrainz: Direct API client with user token auth
- musicbrainz: Entity resolution with rate limiting

Features:
- `cache` (default): SQLite persistence for offline support
  - ListenBrainz: Credential storage, offline listen queue
  - MusicBrainz: Resolved entity cache with TTL

Architecture:
- Pure async Rust, no Tauri dependencies
- Shared error types via IntegrationError enum
- Rate limiting for MusicBrainz (1 req/sec direct, faster via proxy)
2026-02-15 10:36:53 -06:00
vicrodh 0911cf75df refactor(backend): Phase 0 - Initialize multi-crate workspace
Create the foundational workspace structure for the multi-crate
architecture refactor. This phase establishes placeholder crates
that will be populated in subsequent phases.

Crates created:
- qbz-models: Shared types, events, and traits (Tier 0)
- qbz-audio: Audio backend system (Tier 1)
- qbz-player: Playback engine and queue (Tier 1)
- qbz-qobuz: Qobuz API client (Tier 1)
- qbz-core: Orchestrator (Tier 3)

All crates compile successfully with placeholder implementations.
The workspace uses shared dependencies via workspace.dependencies.

cc
2026-02-14 12:48:52 -06:00