Commit Graph
411 Commits
Author SHA1 Message Date
vicrodh 4c606e029e feat(favorites): implement local SQLite persistence for favorites
This ensures favorites sync properly across all views (album detail,
continue listening, etc.) by using local persistence as the source
of truth for UI, with API calls for actual operations.

Backend (Rust):
- Add favorites_cache.rs module with SQLite tables:
  - favorite_tracks (track_id INTEGER PRIMARY KEY)
  - favorite_albums (album_id TEXT PRIMARY KEY)
  - favorite_artists (artist_id INTEGER PRIMARY KEY)
- Add Tauri commands for cache operations:
  - get_cached_favorite_* - read from local cache
  - cache_favorite_* / uncache_favorite_* - add/remove single items
  - sync_cached_favorite_* - bulk sync from API data
  - clear_favorites_cache - for logout

Frontend:
- Rewrite favoritesStore.ts to use persistence:
  - loadFavorites(): Load from cache first, then sync from API
  - toggleFavorite(): API call first → update cache on success
  - No optimistic updates - UI shows loading state until confirmed
- Rewrite albumFavoritesStore.ts with same pattern
- Create artistFavoritesStore.ts for artist favorites
- Update +page.svelte to load all favorites on login
- Update FavoritesView to sync cache when loading from API

Sync strategy:
- On login: Fetch from Qobuz API → sync to local cache
- On toggle: API call → on success → update local cache → notify UI
- FavoritesView reads from API and syncs local cache
- All other views read from local cache (instant UI)
2026-01-29 19:42:04 -06:00
vicrodh 19d84326b8 fix: enable resume for streaming tracks
Previously, streaming tracks could not be resumed after pause timeout
because current_audio_data was never set for streaming playback.

Changes:
- Store BufferedMediaSource in current_streaming_source for streaming tracks
- On resume, check if streaming source has complete data and use it
- Clear streaming source on Stop and when switching to non-streaming play
- Log buffered bytes when streaming not complete yet

This allows users to pause streaming tracks and resume them after the
download completes, instead of getting "no audio data available" error.
2026-01-29 11:23:58 -06:00
vicrodh 56faddf2dc chore: update app logo to v3
New logo design with vinyl/gear motif, replacing old GPT-generated logo.
Updated all icon sizes for Tauri, Windows Store, favicon, and static assets.
2026-01-29 01:22:05 -06:00
vicrodh 7ed3dac7f5 fix(library): reduce artwork cache bloat with thumbnails
- metadata.rs now creates 500px thumbnails instead of copying full images
- Added cache management commands: library_get_cache_stats,
  library_clear_artwork_cache, library_clear_thumbnails_cache
- Thumbnails stored in ~/.local/share/qbz/thumbnails/ (was ~/.cache/qbz/artwork)
- Reduces cache size from GB to MB for large libraries
2026-01-28 11:24:19 -06:00
vicrodh a6fc264903 fix(playback): persist streaming quality setting
Quality was hardcoded to UltraHiRes in backend. Now:
- Frontend passes quality from localStorage to play_track command
- Backend parses quality string to Quality enum
- All prefetch and stream URL calls use the user's preferred quality
2026-01-28 11:24:11 -06:00
vicrodh 700dc07014 fix(library): improve artist consolidation and album matching
- Add all_artists field to LocalAlbum for accurate artist matching
- Consolidate duplicate artists with case differences (e.g., "Alice in Chains" / "Alice In Chains")
- Fix album count calculation to avoid double-counting
- Show Various Artists albums under contributing artists
- Handle multi-artist names (e.g., "A, B, C") with union matching
- Add cleanup missing files feature in settings maintenance section
- Improve settings UI with side-by-side Maintenance/Danger Zone layout
- Add status indicator during cleanup operations
2026-01-28 08:13:07 -06:00
vicrodh e8f0c96ccc feat(library): add source badge to album cards in Local Library
Albums now show a small badge indicating their source:
- HardDrive icon for local files (source='user')
- Qobuz logo for offline cached content (source='qobuz_download')

Badge appears in bottom-right corner of album artwork.
2026-01-28 02:40:00 -06:00
vicrodh a483c766ab fix(offline): clean up empty folders when removing cached tracks
When deleting a cached track (individually, via clear all, or via LRU
eviction), the audio file was deleted but cover.jpg remained orphaned.

Now the system removes the entire album folder if no audio files remain,
and recursively removes the artist folder if it also becomes empty.
2026-01-27 19:36:15 -06:00
vicrodh fab1ff3003 fix(suggestions): wrap artist_id in Some for Option type 2026-01-27 17:13:03 -06:00
vicrodh 79c68d292e feat(suggestions): improve vector store and add cache management
- Add get_all_related_artists for direct relationship lookup
- Add get_related_artists helper method
- Add clear_all method to vector store for cache management
- Add clear_vector_store Tauri command
- Add Artist Vectors cache section to Settings view
- Use artist_info tuples (mbid, name) for better track searching
2026-01-27 16:54:11 -06:00
vicrodh 9a89074b5f feat(suggestions): add interactive navigation and track info modal
- Add artist_id field to SuggestedTrack for navigation
- Make artist names clickable to navigate to artist view
- Make album names clickable to navigate to album view
- Add TrackInfo button (info icon) to view track credits
- Integrate TrackInfoModal component with suggestions
- Update PlaylistDetailView to pass navigation callbacks
2026-01-27 16:53:56 -06:00
vicrodh 54315c2e4e fix(suggestions): fix deadlock in build_vector
The build_vector function was calling build_from_musicbrainz and
build_from_qobuz while holding the store lock. These functions also
try to acquire the store lock, causing a deadlock.

Fixed by saving the vectors from the first call and reusing them
for storage, instead of calling the functions again.
2026-01-27 14:42:10 -06:00
vicrodh 7e76fd6925 debug: add info-level logging to build_from_musicbrainz 2026-01-27 14:22:40 -06:00
vicrodh d90edc4feb debug(suggestions): add more logging to find deadlock 2026-01-27 13:40:17 -06:00
vicrodh 5f13617ae7 debug(suggestions): add logging to vector building
Added info-level logs to track:
- ensure_vector start/success/failure
- build_from_musicbrainz cache hit/API fetch
2026-01-27 13:35:18 -06:00
vicrodh 12d3c6b654 style: remove emojis from log messages 2026-01-27 13:26:57 -06:00
vicrodh b551df71c0 perf(suggestions): limit to top 5 artists and reduce rate limiting
Major optimizations:
- Extract only top 5 artists by track frequency instead of all artists
- Use faster rate limiter (200ms) when using MusicBrainz proxy
- Remove skip_vector_build since we now have manageable artist count

Expected load time: ~5-8 seconds for first load with 5 artists
vs previous: 50+ seconds with 50 artists
2026-01-27 13:20:16 -06:00
vicrodh a49cdf483a perf(suggestions): skip MusicBrainz API calls when skip_vector_build=true
When skip_vector_build is enabled, also skip network calls for MBID
resolution of uncached artists. This prevents rate limiting (429) errors
that were causing 55+ second delays on initial load.

Tradeoff: First load with empty cache may have no results, but
subsequent loads will be instant using cached data.
2026-01-27 12:31:51 -06:00
vicrodh ff9e11db5c perf(suggestions): add skip_vector_build option for faster initial load
- Add skip_vector_build config option to SuggestionConfig
- When true, skips ensure_vector calls (which can be very slow)
- Uses only cached/existing vectors for similarity search
- Frontend now uses skip_vector_build=true for initial load

- Add timestamped logging helper in frontend component
2026-01-27 12:23:05 -06:00
vicrodh 541c93f06d fix(suggestions): fix config deserialization and effect loop
- Add #[serde(default)] to SuggestionConfig to allow partial config
  from frontend (fixes "missing field max_artists" error)

- Fix effect loop in PlaylistSuggestions.svelte:
  - Add loadingInitiated flag to prevent re-triggering
  - Add cleanup effect when playlistId changes
  - Prevent concurrent loading attempts
2026-01-27 12:07:08 -06:00
vicrodh 1560cd4e3d debug(suggestions): add detailed timing logs for debugging
Frontend logging:
- Log when effect triggers with artist count
- Log when backend call starts/finishes with timing
- Log result details (tracks, artists counts)

Backend logging (playlist_suggestions.rs):
- Log MBID resolution with cache hits/misses/failures
- Log config values
- Log engine call timing

Engine logging (suggestions.rs):
- Step 1: ensure_vector timing per artist
- Step 2: playlist vector computation
- Step 3: find_nearest timing
- Step 4: track search timing per artist
- Early exit when pool size reached
2026-01-27 12:02:31 -06:00
vicrodh e9a5dfcb80 perf(suggestions): optimize loading with backend MBID resolution
- Move MBID resolution from frontend to backend
  - Eliminates multiple sequential IPC calls
  - Uses MusicBrainz cache for faster resolution
  - Logs resolution progress for debugging

- Implement progressive loading in UI
  - Initial load: 18 tracks (3 pages)
  - "Load more" expands to 60 tracks on demand
  - Better feedback during loading states

- Fix refresh button styling
  - Remove background while spinning
  - Update title based on available actions

- Add "Load more" link in pagination area
2026-01-27 11:58:54 -06:00
vicrodh cadb346e32 feat(suggestions): add vector-based playlist suggestions engine
- Add suggestions.rs with SuggestionsEngine for generating suggestions
- Add SuggestionConfig with configurable parameters
- Add SuggestedTrack and SuggestionResult types
- Integrate with MusicBrainz relationships and Qobuz track search
- Add Tauri commands: get_playlist_suggestions_v2, get_vector_store_stats
- Add cleanup_vector_store command for maintenance
- Add Serialize to StoreStats for IPC
- Register ArtistVectorStoreState in app initialization
- 23 tests passing
2026-01-27 11:38:56 -06:00
vicrodh f3dcb6ebf2 feat(vectors): add vector builder with MB and Qobuz sources
- Add weights.rs with configurable RelationshipWeights
- Add builder.rs with ArtistVectorBuilder for constructing vectors
- Integrate MusicBrainz relationships (members, groups, collaborators)
- Integrate Qobuz similar artists API
- Add ensure_vector() with TTL for lazy building
- Add resolve_qobuz_to_mbid() for ID resolution via name search
- 21 tests passing (sparse_vector, store, weights, builder)
2026-01-27 11:30:06 -06:00
vicrodh 1d2e28c1a8 feat(vectors): add sparse vector foundation for playlist suggestions
Phase 1 of playlist-suggestions feature:
- SparseVector: memory-efficient vector with math operations
  - set/get with sorted indices for O(log n) access
  - add, sub, scale, dot product, magnitude
  - normalize, cosine_similarity, top_k_similar
- ArtistVectorStore: SQLite persistence
  - artist_index table: MBID to integer index mapping
  - vector_entries table: sparse representation (one row per non-zero)
  - vector_metadata table: track freshness with TTL
  - find_nearest: cosine similarity search with exclusion
- 17 unit tests all passing
2026-01-27 11:19:55 -06:00
vicrodh b3117cc7c2 Bump version to 1.1.7 2026-01-27 04:58:28 -06:00
vicrodh 561f7487d0 Add Flatpak welcome modal and sequential modal priority system
- Add FlatpakWelcomeModal component shown once for Flatpak users
- Add persistence for Flatpak welcome shown state (updates.db)
- Implement sequential modal priority: Flatpak > What's new > Update available
- Add Flatpak section to Settings navigation (only when running in Flatpak)
- Clean up duplicate Flatpak section and invalid style block in SettingsView
- Add copyable command styling for Flatpak section
2026-01-27 04:37:17 -06:00
vicrodh b664a5afd0 Change defaults: streaming ON, check for updates OFF 2026-01-27 03:59:32 -06:00
vicrodh 54740ee225 feat(updates): add update checks and what's new modal
cx
2026-01-27 02:59:46 -06:00
vicrodh 426584aa59 feat(backend): add custom track order persistence for playlists
- Create playlist_track_custom_order table for storing user-defined track positions
- Add database functions: get, init, set, move track, has, clear custom order
- Add Tauri commands for frontend access
- Migration-safe for existing databases

cc
2026-01-26 21:25:25 -06:00
vicrodh 65adf352b6 feat(TagEditor): show real-time progress when writing tags
Backend:
- Emit 'library:tag_write_progress' events with current/total count
- Progress updates as each file is written

Frontend:
- Listen for progress events during direct write mode
- Save button shows "Writing X of Y..." dynamically
- Cleanup listener on completion or error
2026-01-26 20:46:38 -06:00
vicrodh e0d2439f56 fix: correct Discogs adapter to use extended search result
- Remove duplicate discogs_release_to_search_result function
- Use existing discogs_extended_to_search_result which properly maps:
  year, country, label, catno, format
- MusicBrainz already maps label_info with catalog_number
2026-01-26 20:30:11 -06:00
vicrodh fd08dc8f2c feat(TagEditor): add track count and descriptive result status
Backend:
- Add media/track-count fields to MusicBrainz ReleaseResult model
- Extract track_count and format from search results

Frontend:
- Show "{n} results" or "No results, try different search"
- Auto-expand results panel when search returns data
- Track hasSearched state for proper status display
2026-01-26 20:14:33 -06:00
vicrodh dddb4d4fd1 fix: resolve type mismatches and add Phase 6 polish
- Fix RemoteTrackMetadata field types (disc_number, track_number as u8)
- Fix RemoteAlbumMetadata fields (genres, tracks as Vec, disc_count as u8)
- Fix cache method calls (async, proper signatures)
- Fix error variant construction (ProviderUnavailable, InvalidProviderId)
- Add track count mismatch warning in UI
- Add rate limit error message handling
- Add 'Open in browser' button for selected result
2026-01-26 18:55:05 -06:00
vicrodh 6d20baf914 feat: add remote metadata service and Tauri commands
Phase 4 of service integration:
- Add provider adapters (MusicBrainz, Discogs to unified DTOs)
- Add RemoteMetadataState with search_albums and get_album_metadata
- Create remote_metadata commands module with Tauri handlers
- Wire up state and commands in lib.rs

Available commands:
- remote_metadata_search: Search albums on MusicBrainz or Discogs
- remote_metadata_get_album: Get full album metadata with tracks
- remote_metadata_cache_stats: Get cache statistics
- remote_metadata_clear_cache: Clear the in-memory cache
2026-01-26 18:46:29 -06:00
vicrodh 25e877181a discogs: add search_releases and get_release_metadata methods
Phase 3 of service integration:
- Add DiscogsReleaseMetadata struct with full album info
- Add DiscogsArtist, DiscogsLabel, DiscogsTrack structs
- Add DiscogsSearchResultExtended for detailed search results
- Add search_releases() for release search with extended metadata
- Add get_release_metadata() for full release with tracklist
2026-01-26 18:42:41 -06:00
vicrodh b8340ad6c7 musicbrainz: add get_release_with_tracks with media/tracks support
- Add ReleaseFullResponse model with media, tracks, and tags
- Add Medium, MediumTrack, TrackRecording, Tag models
- Add get_release_with_tracks() for fetching full release details
- Add search_releases_extended() with configurable limit and catalog number
- Export new types from module
2026-01-26 18:39:47 -06:00
vicrodh 70df504af3 library: add remote_metadata module structure and unified DTOs
- Add RemoteProvider enum (MusicBrainz, Discogs)
- Add RemoteAlbumSearchResult for lightweight search results
- Add RemoteAlbumMetadata for full metadata with tracks
- Add RemoteTrackMetadata for track info
- Add RemoteMetadataCache with TTL-based expiration
- Add adapter functions for MusicBrainz and Discogs
- Add Discogs position/duration parsing utilities
2026-01-26 18:37:45 -06:00
vicrodh 5d812f4966 feat(library): add LocalLibrary tag editor backend
cx
2026-01-26 13:24:51 -06:00
Victor RH 19aa5ca4a1 Adds Flatpak sandbox instructions
Provides instructions and a UI section to guide users on granting filesystem access and network sharing permissions to the Flatpak version of the app.

Removes redundant home directory permission in tauri config.
2026-01-26 11:34:32 -06:00
vicrodh 2f4b79f1bf fix(cast): cleanup Chromecast connection on app exit 2026-01-26 11:01:14 -06:00
vicrodh 1384a1d276 feat(cast): add position tracking and auto-advance for Chromecast
- Add cast_get_position command for Chromecast media position
- Add CastPositionInfo struct with position, duration, player_state, idle_reason
- Extend position polling to work with both DLNA and Chromecast
- Detect track end on Chromecast via IDLE state with FINISHED reason
- Unify polling logic for both protocols

cpvsc
2026-01-26 10:41:35 -06:00
vicrodh 71ae2f92c1 feat(dlna): add position tracking and seekbar support
DLNA POSITION TRACKING:
- Added GetPositionInfo SOAP action to query current playback position
- Added GetTransportInfo SOAP action to get transport state (PLAYING/PAUSED/STOPPED)
- New dlna_get_position command returns position, duration, and transport state
- Added parse_time_string() helper for HH:MM:SS format

FRONTEND INTEGRATION:
- castStore: Added position polling (1s interval) when connected to DLNA
- castStore: New getCastPosition() for UI components
- castStore: Automatic polling start/stop on track play/disconnect
- playerStore: Subscribes to castStore for position updates
- playerStore: Updates currentTime from cast position when casting

This enables the seekbar to display accurate position during DLNA playback.
Seek functionality was already implemented (dlna_seek command).

Files modified:
- src-tauri/src/cast/dlna/device.rs
- src-tauri/src/cast/dlna/commands.rs
- src-tauri/src/cast/dlna/mod.rs
- src-tauri/src/lib.rs
- src/lib/stores/castStore.ts
- src/lib/stores/playerStore.ts
2026-01-26 09:41:11 -06:00
vicrodh 003468a28e fix(dlna): use fixed port 9876 and add extensive logging
CHANGES:
- MediaServer now uses fixed port 9876 (falls back to random if busy)
  This improves firewall compatibility for DLNA casting
- Added timeout (10s) for SOAP actions to prevent deadlocks
- Added extensive logging throughout DLNA flow:
  - Command entry point logging
  - MediaServer lifecycle (start, thread, requests)
  - SOAP action responses
  - Content-type and DIDL metadata details

VERIFIED WORKING:
- DLNA device discovery ✓
- Connection to gmediarender ✓
- SetAVTransportURI SOAP action ✓
- Play SOAP action ✓
- MediaServer serving audio to DLNA renderer ✓

Files modified:
- src-tauri/src/cast/dlna/device.rs
- src-tauri/src/cast/dlna/commands.rs
- src-tauri/src/cast/media_server.rs
2026-01-26 09:30:00 -06:00
vicrodh 892341b773 fix(dlna): use dynamic protocolInfo and add debug logging
PROBLEM:
- DLNA device detection and connection worked
- Audio playback on DLNA devices did not work

ROOT CAUSE:
- DIDL-Lite metadata had hardcoded protocolInfo='audio/flac'
- Many DLNA devices silently reject content when protocolInfo
  doesn't match the actual MIME type
- No debug logging to diagnose SOAP action failures

FIX:
- Pass actual content_type from Qobuz stream to DIDL builder
- Generate protocolInfo dynamically based on real MIME type
- Add extensive debug logging for:
  - MediaServer URL generated
  - Content-Type from Qobuz
  - Complete DIDL-Lite metadata
  - SOAP action responses (SetAVTransportURI, Play)
  - Error details when actions fail

Files modified:
- src-tauri/src/cast/dlna/device.rs
- src-tauri/src/cast/dlna/commands.rs
2026-01-26 07:56:08 -06:00
vicrodh 078f33085f fix: check studio albums count, not total albums
The previous logic checked total album count (< 10) to decide whether
to compare "X" vs "The X" variants. This failed for artists like
"Traveling Wilburys" (19 total albums, but 0 studio albums).

Now we:
1. Always count studio albums for the initial match
2. If studio albums < 10, search for "The X" variant
3. Compare studio album counts

Example fix:
- "Traveling Wilburys": 19 total, 0 studio albums
- "The Traveling Wilburys": 17 total, 6 studio albums
- Now correctly prefers "The Traveling Wilburys"
2026-01-25 23:14:59 -06:00
vicrodh fbc1938838 feat: compare studio albums only when matching artists
When comparing "X" vs "The X" variants, now fetches actual albums
and counts only studio albums (main discography) instead of total
album count. This filters out:
- Tributes (albums by other artists)
- Compilations (greatest hits, best of)
- Unofficial releases (bootlegs, broadcasts)
- Live albums
- EPs and singles

Uses the same categorization logic as the frontend (qobuzAdapters.ts).
This provides cleaner matching, e.g., "The Beatles" with 200+ studio
albums vs "Beatles" tribute compilation with 2 albums.
2026-01-25 23:08:46 -06:00
vicrodh c6c18460d0 fix: handle "The" prefix variations in artist matching
MusicBrainz often returns names without "The" prefix (e.g., "Beatles",
"Traveling Wilburys") but Qobuz has both variants, with the prefixed
version being the legitimate artist.

New matching logic:
1. Search for exact name match first
2. If no exact match, check for "The X" variant in results
3. If match has <5 albums, search for "The X" specifically and compare
4. Prefer the variant with more albums (likely the real artist)

This fixes navigation to "The Beatles", "The Traveling Wilburys", etc.
2026-01-25 22:37:37 -06:00
vicrodh 74d2059b8a fix: use correct fields from AlbumSummary in musician appearances
AlbumSummary only has id, title, image, and label fields.
The code was incorrectly trying to access artist and
release_date_original which only exist on the full Album struct.

- Use track.performer for artist_name instead of album.artist
- Set year to None since AlbumSummary doesn't have release_date
- Fix image access since ImageSet is direct field, not Option
- Fix title access since String is direct field, not Option
2026-01-25 22:18:15 -06:00
vicrodh fc138d801c feat(backend): add musician resolution types and commands
Phase 1 of Musician Page implementation:

Types (musicbrainz/models.rs):
- MusicianConfidence enum (Confirmed/Contextual/Weak/None)
- ResolvedMusician struct with confidence assessment
- AlbumAppearance and MusicianAppearances for 'Appears On'

Commands (commands/musician.rs):
- resolve_musician: Determines confidence level and destination
- get_musician_appearances: Returns deduplicated album appearances

Confidence logic:
- Level 3 (Confirmed): Has Qobuz artist page with albums
- Level 2 (Contextual): Appears in 3+ album credits
- Level 1 (Weak): Sparse Qobuz results
- Level 0 (None): No safe match
2026-01-25 22:06:16 -06:00