Merge refactor/internal/v2-cleanroom-final into pre-release

This commit is contained in:
vicrodh committed 2026-04-26 13:07:17 -06:00
commit fd545eccaa
18 files changed
+533 -27

No files matched your search

+37
View File
@@ -18,6 +18,43 @@ pub enum AudioDevicePresence {
Inconclusive,
}
/// Snapshot of which output device is currently in use and whether we're playing.
/// Mirrors the legacy `AudioOutputStatus` shape so the frontend type is unchanged.
#[derive(Debug, serde::Serialize)]
pub struct V2AudioOutputStatus {
pub device_name: Option<String>,
pub is_playing: bool,
}
/// Get current audio output status (V2 — reads from CoreBridge.player state).
///
/// Returns the device name actually in use by the audio thread plus whether
/// playback is currently active. Used by AudioOutputBadges to render the
/// active sink + DAC indicators in the now-playing bar.
#[tauri::command]
pub async fn v2_get_audio_output_status(
bridge: State<'_, CoreBridgeState>,
) -> Result<V2AudioOutputStatus, RuntimeError> {
let bridge_guard = bridge.get().await;
let player = bridge_guard.player();
Ok(V2AudioOutputStatus {
device_name: player.state.current_device(),
is_playing: player.state.is_playing(),
})
}
/// List the available CPAL output sinks (V2).
///
/// Delegates enumeration to `qbz_audio::output_sinks::list_output_sinks`,
/// which returns the same `{name, description, volume, is_default}` shape
/// the legacy `get_pipewire_sinks` command returned. Used by the audio
/// settings UI and AudioOutputBadges to populate the device picker and
/// label the currently-routed sink.
#[tauri::command]
pub fn v2_get_pipewire_sinks() -> Result<Vec<qbz_audio::OutputSinkInfo>, RuntimeError> {
qbz_audio::list_output_sinks().map_err(RuntimeError::Internal)
}
/// Snapshot the presence of the user's currently-selected output
/// device. Frontend calls this on demand (e.g. when a
/// `audio:device-missing` toast button fires Retry).
+125 -4
View File
@@ -1,10 +1,10 @@
use tauri::State;
use qbz_models::{
Album, Artist, AwardPageData, DiscoverAlbum, DiscoverData, DiscoverPlaylistsResponse,
DiscoverResponse, GenreInfo, LabelExploreResponse, LabelGetListResponse, LabelListPage,
LabelPageData, LabelStoryResponse, PageArtistResponse, Playlist, PlaylistTag,
SearchResultsPage, Track,
Album, Artist, ArtistAlbums, AwardPageData, DiscoverAlbum, DiscoverData,
DiscoverPlaylistsResponse, DiscoverResponse, GenreInfo, LabelExploreResponse,
LabelGetListResponse, LabelListPage, LabelPageData, LabelStoryResponse, PageArtistResponse,
Playlist, PlaylistTag, ReleasesGridResponse, SearchResultsPage, Track, TracksContainer,
};
use crate::artist_blacklist::BlacklistState;
@@ -399,6 +399,127 @@ pub async fn v2_get_artist_with_albums(
.map_err(RuntimeError::Internal)
}
/// Get an artist's albums collection (V2 - returns only the `ArtistAlbums` envelope).
#[tauri::command]
#[allow(non_snake_case)]
pub async fn v2_get_artist_albums(
artistId: u64,
limit: Option<u32>,
offset: Option<u32>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<ArtistAlbums, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
log::info!(
"[V2] get_artist_albums: {} limit={:?} offset={:?}",
artistId,
limit,
offset
);
let bridge = bridge.get().await;
bridge
.get_artist_albums(artistId, limit, offset)
.await
.map_err(RuntimeError::Internal)
}
/// Get artist detail with albums, playlists and appears-on tracks (V2 - uses QbzCore).
#[tauri::command]
#[allow(non_snake_case)]
pub async fn v2_get_artist_detail(
artistId: u64,
limit: Option<u32>,
offset: Option<u32>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<Artist, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
let limit = limit.or(Some(1000));
let offset = offset.or(Some(0));
log::info!(
"[V2] get_artist_detail: {} limit={:?} offset={:?}",
artistId,
limit,
offset
);
let bridge = bridge.get().await;
bridge
.get_artist_detail(artistId, limit, offset)
.await
.map_err(RuntimeError::Internal)
}
/// Get an artist's popular/top tracks (V2 - uses QbzCore).
#[tauri::command]
#[allow(non_snake_case)]
pub async fn v2_get_artist_tracks(
artistId: u64,
limit: Option<u32>,
offset: Option<u32>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<TracksContainer, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
let limit = limit.unwrap_or(50);
let offset = offset.unwrap_or(0);
log::info!(
"[V2] get_artist_tracks: {} limit={} offset={}",
artistId,
limit,
offset
);
let bridge = bridge.get().await;
bridge
.get_artist_tracks(artistId, limit, offset)
.await
.map_err(RuntimeError::Internal)
}
/// Get an artist's releases grid paginated by release_type (V2 - uses QbzCore).
#[tauri::command]
#[allow(non_snake_case)]
pub async fn v2_get_releases_grid(
artistId: u64,
releaseType: String,
limit: Option<u32>,
offset: Option<u32>,
sort: Option<String>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<ReleasesGridResponse, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
let limit = limit.unwrap_or(25);
let offset = offset.unwrap_or(0);
log::info!(
"[V2] get_releases_grid: {} type={} limit={} offset={}",
artistId,
releaseType,
limit,
offset
);
let bridge = bridge.get().await;
bridge
.get_releases_grid(artistId, &releaseType, limit, offset, sort.as_deref())
.await
.map_err(RuntimeError::Internal)
}
/// Get label page (aggregated: top tracks, releases, playlists, artists)
#[tauri::command]
#[allow(non_snake_case)]
@@ -1299,6 +1299,13 @@ pub fn v2_set_qobuz_tos_accepted(
crate::config::legal_settings::set_qobuz_tos_accepted(state, accepted)
}
#[tauri::command]
pub fn v2_get_qobuz_tos_accepted(
state: State<'_, crate::config::legal_settings::LegalSettingsState>,
) -> Result<bool, String> {
crate::config::legal_settings::get_qobuz_tos_accepted(state)
}
#[tauri::command]
pub fn v2_set_update_check_on_launch(
enabled: bool,
@@ -1382,6 +1389,13 @@ pub fn v2_mark_flatpak_welcome_shown(
crate::updates::mark_flatpak_welcome_shown(state)
}
#[tauri::command]
pub fn v2_has_flatpak_welcome_been_shown(
state: State<'_, crate::updates::UpdatesState>,
) -> Result<bool, String> {
crate::updates::has_flatpak_welcome_been_shown(state)
}
#[tauri::command]
pub fn v2_get_backend_logs() -> Vec<String> {
crate::logging::get_backend_logs()
@@ -1469,6 +1483,16 @@ pub fn v2_is_running_in_flatpak() -> bool {
crate::flatpak::is_running_in_flatpak()
}
#[tauri::command]
pub fn v2_get_flatpak_help_text() -> String {
crate::flatpak::get_flatpak_guidance()
}
#[tauri::command]
pub fn v2_detect_desktop_theme() -> crate::desktop_theme::DesktopThemeInfo {
crate::desktop_theme::detect_desktop_theme()
}
#[tauri::command]
pub fn v2_is_auto_update_eligible() -> bool {
crate::updates::is_auto_update_eligible()
@@ -1486,6 +1510,13 @@ pub fn v2_mark_snap_welcome_shown(
crate::updates::mark_snap_welcome_shown(state)
}
#[tauri::command]
pub fn v2_has_snap_welcome_been_shown(
state: State<'_, crate::updates::UpdatesState>,
) -> Result<bool, String> {
crate::updates::has_snap_welcome_been_shown(state)
}
#[tauri::command]
pub async fn v2_detect_legacy_cached_files(
cache_state: State<'_, OfflineCacheState>,
+59 -4
View File
@@ -13,10 +13,11 @@ use tokio::sync::RwLock;
use qbz_audio::{settings::AudioSettingsStore, AudioDiagnostic, AudioSettings, VisualizerTap};
use qbz_core::QbzCore;
use qbz_models::{
Album, Artist, DiscoverAlbum, DiscoverData, DiscoverPlaylistsResponse, DiscoverResponse,
GenreInfo, LabelExploreResponse, LabelGetListResponse, LabelListPage, LabelPageData,
LabelStoryResponse, PageArtistResponse, Playlist, PlaylistTag, Quality,
QueueState, QueueTrack, RepeatMode, SearchResultsPage, StreamUrl, Track, UserSession,
Album, Artist, ArtistAlbums, DiscoverAlbum, DiscoverData, DiscoverPlaylistsResponse,
DiscoverResponse, GenreInfo, LabelExploreResponse, LabelGetListResponse, LabelListPage,
LabelPageData, LabelStoryResponse, PageArtistResponse, Playlist, PlaylistTag, Quality,
QueueState, QueueTrack, ReleasesGridResponse, RepeatMode, SearchResultsPage, StreamUrl, Track,
TracksContainer, UserSession,
};
use qbz_player::{PlaybackState, Player};
@@ -555,6 +556,60 @@ impl CoreBridge {
.map_err(|e| e.to_string())
}
/// Get an artist's albums collection (paginated `ArtistAlbums` only).
pub async fn get_artist_albums(
&self,
artist_id: u64,
limit: Option<u32>,
offset: Option<u32>,
) -> Result<ArtistAlbums, String> {
self.core
.get_artist_albums(artist_id, limit, offset)
.await
.map_err(|e| e.to_string())
}
/// Get artist detail (albums, playlists, appears-on tracks).
pub async fn get_artist_detail(
&self,
artist_id: u64,
limit: Option<u32>,
offset: Option<u32>,
) -> Result<Artist, String> {
self.core
.get_artist_detail(artist_id, limit, offset)
.await
.map_err(|e| e.to_string())
}
/// Get an artist's popular/top tracks.
pub async fn get_artist_tracks(
&self,
artist_id: u64,
limit: u32,
offset: u32,
) -> Result<TracksContainer, String> {
self.core
.get_artist_tracks(artist_id, limit, offset)
.await
.map_err(|e| e.to_string())
}
/// Get an artist's releases grid (paginated by `release_type`).
pub async fn get_releases_grid(
&self,
artist_id: u64,
release_type: &str,
limit: u32,
offset: u32,
sort: Option<&str>,
) -> Result<ReleasesGridResponse, String> {
self.core
.get_releases_grid(artist_id, release_type, limit, offset, sort)
.await
.map_err(|e| e.to_string())
}
/// Get label page (aggregated: top tracks, releases, playlists, artists)
pub async fn get_label_page(&self, label_id: u64) -> Result<LabelPageData, String> {
self.core
+11
View File
@@ -1398,6 +1398,7 @@ pub fn run() {
commands::playback::get_pipewire_sinks,
commands::playback::get_audio_output_status,
flatpak::get_flatpak_help_text,
commands_v2::v2_get_flatpak_help_text,
config::legal_settings::get_qobuz_tos_accepted,
updates::has_flatpak_welcome_been_shown,
updates::has_snap_welcome_been_shown,
@@ -1579,6 +1580,7 @@ pub fn run() {
commands_v2::v2_is_running_in_flatpak,
commands_v2::v2_is_running_in_snap,
commands_v2::v2_mark_snap_welcome_shown,
commands_v2::v2_has_snap_welcome_been_shown,
commands_v2::v2_detect_legacy_cached_files,
commands_v2::v2_reco_log_event,
commands_v2::v2_reco_train_scores,
@@ -1625,6 +1627,7 @@ pub fn run() {
commands_v2::v2_library_refresh_album_metadata_from_files,
commands_v2::v2_factory_reset,
commands_v2::v2_set_qobuz_tos_accepted,
commands_v2::v2_get_qobuz_tos_accepted,
commands_v2::v2_get_update_preferences,
commands_v2::v2_get_current_version,
commands_v2::v2_check_for_updates,
@@ -1636,6 +1639,7 @@ pub fn run() {
commands_v2::v2_has_whats_new_been_shown,
commands_v2::v2_mark_whats_new_shown,
commands_v2::v2_mark_flatpak_welcome_shown,
commands_v2::v2_has_flatpak_welcome_been_shown,
commands_v2::v2_is_auto_update_eligible,
commands_v2::v2_get_backend_logs,
commands_v2::v2_upload_logs_to_paste,
@@ -1699,6 +1703,10 @@ pub fn run() {
commands_v2::v2_get_artist_page,
commands_v2::v2_get_similar_artists,
commands_v2::v2_get_artist_with_albums,
commands_v2::v2_get_artist_albums,
commands_v2::v2_get_artist_detail,
commands_v2::v2_get_artist_tracks,
commands_v2::v2_get_releases_grid,
commands_v2::v2_get_label_page,
commands_v2::v2_get_label_explore,
commands_v2::v2_get_label_albums,
@@ -1723,6 +1731,8 @@ pub fn run() {
commands_v2::v2_prefetch_track,
commands_v2::v2_reinit_audio_device,
commands_v2::v2_check_audio_device_presence,
commands_v2::v2_get_audio_output_status,
commands_v2::v2_get_pipewire_sinks,
commands_v2::v2_get_audio_settings,
commands_v2::v2_set_audio_output_device,
commands_v2::v2_set_audio_exclusive_mode,
@@ -1868,6 +1878,7 @@ pub fn run() {
commands_v2::v2_clear_image_cache,
// Desktop theme detection (KDE/Klassy → adaptive window controls)
desktop_theme::detect_desktop_theme,
commands_v2::v2_detect_desktop_theme,
// Mixtapes & Collections
commands_v2::v2_list_mixtape_collections,
commands_v2::v2_get_mixtape_collection,