feat(blacklist): integrate filtering into search and radio commands
Phase 2 (part 1) - Read-only integration: - Filter blacklisted artists from search_tracks, search_artists, search_all - Filter blacklisted artists from get_similar_artists - Filter most_popular item if blacklisted - Add blacklist filtering to all radio commands (artist/track/infinite/refill) - Use output-layer filtering for radio (engine internals unchanged) cc
This commit is contained in:
1 parent
26c5a1b0a3
commit
bec6907dbe
2 files changed
+192
-13
No files matched your search
@@ -5,7 +5,8 @@
|
||||
use tauri::State;
|
||||
use tokio::task;
|
||||
|
||||
use crate::api::{QobuzClient, Track};
|
||||
use crate::api::Track;
|
||||
use crate::artist_blacklist::BlacklistState;
|
||||
use crate::playback_context::{ContentSource, ContextType, PlaybackContext};
|
||||
use crate::queue::QueueTrack;
|
||||
use crate::radio_engine::{BuildRadioOptions, RadioEngine, RadioPoolBuilder};
|
||||
@@ -20,6 +21,7 @@ pub async fn create_artist_radio(
|
||||
artist_id: u64,
|
||||
artist_name: String,
|
||||
state: State<'_, AppState>,
|
||||
blacklist_state: State<'_, BlacklistState>,
|
||||
) -> Result<String, String> {
|
||||
log::info!("[Radio] Creating artist radio for: {} (ID: {})", artist_name, artist_id);
|
||||
|
||||
@@ -83,11 +85,24 @@ pub async fn create_artist_radio(
|
||||
.await
|
||||
.map_err(|e| format!("Track generation task failed: {}", e))??;
|
||||
|
||||
// Fetch full track details from Qobuz
|
||||
// Fetch full track details from Qobuz, filtering blacklisted artists
|
||||
let mut tracks = Vec::new();
|
||||
let mut blacklist_skipped = 0;
|
||||
for track_id in track_ids {
|
||||
match client.get_track(track_id).await {
|
||||
Ok(track) => {
|
||||
// Check if track's artist is blacklisted
|
||||
if let Some(ref performer) = track.performer {
|
||||
if blacklist_state.is_blacklisted(performer.id) {
|
||||
log::debug!(
|
||||
"[Radio] Skipping blacklisted artist track: {} - {}",
|
||||
performer.name,
|
||||
track.title
|
||||
);
|
||||
blacklist_skipped += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
tracks.push(track);
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -96,6 +111,10 @@ pub async fn create_artist_radio(
|
||||
}
|
||||
}
|
||||
|
||||
if blacklist_skipped > 0 {
|
||||
log::info!("[Radio] Skipped {} tracks from blacklisted artists", blacklist_skipped);
|
||||
}
|
||||
|
||||
if tracks.is_empty() {
|
||||
return Err("Failed to generate any radio tracks".to_string());
|
||||
}
|
||||
@@ -135,6 +154,7 @@ pub async fn create_track_radio(
|
||||
track_name: String,
|
||||
artist_id: u64,
|
||||
state: State<'_, AppState>,
|
||||
blacklist_state: State<'_, BlacklistState>,
|
||||
) -> Result<String, String> {
|
||||
log::info!(
|
||||
"[Radio] Creating track radio for: {} (Track ID: {}, Artist ID: {})",
|
||||
@@ -204,11 +224,24 @@ pub async fn create_track_radio(
|
||||
.await
|
||||
.map_err(|e| format!("Track generation task failed: {}", e))??;
|
||||
|
||||
// Fetch full track details from Qobuz
|
||||
// Fetch full track details from Qobuz, filtering blacklisted artists
|
||||
let mut tracks = Vec::new();
|
||||
let mut blacklist_skipped = 0;
|
||||
for track_id in track_ids {
|
||||
match client.get_track(track_id).await {
|
||||
Ok(track) => {
|
||||
// Check if track's artist is blacklisted
|
||||
if let Some(ref performer) = track.performer {
|
||||
if blacklist_state.is_blacklisted(performer.id) {
|
||||
log::debug!(
|
||||
"[Radio] Skipping blacklisted artist track: {} - {}",
|
||||
performer.name,
|
||||
track.title
|
||||
);
|
||||
blacklist_skipped += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
tracks.push(track);
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -217,6 +250,10 @@ pub async fn create_track_radio(
|
||||
}
|
||||
}
|
||||
|
||||
if blacklist_skipped > 0 {
|
||||
log::info!("[Radio] Skipped {} tracks from blacklisted artists", blacklist_skipped);
|
||||
}
|
||||
|
||||
if tracks.is_empty() {
|
||||
return Err("Failed to generate any radio tracks".to_string());
|
||||
}
|
||||
@@ -301,6 +338,7 @@ fn track_to_queue_track(track: &Track) -> QueueTrack {
|
||||
pub async fn refill_radio_queue(
|
||||
session_id: String,
|
||||
state: State<'_, AppState>,
|
||||
blacklist_state: State<'_, BlacklistState>,
|
||||
) -> Result<u32, String> {
|
||||
log::info!("[Radio] Refilling queue for session: {}", session_id);
|
||||
|
||||
@@ -337,12 +375,25 @@ pub async fn refill_radio_queue(
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Fetch full track details from Qobuz
|
||||
// Fetch full track details from Qobuz, filtering blacklisted artists
|
||||
let client = state.client.lock().await;
|
||||
let mut tracks = Vec::new();
|
||||
let mut blacklist_skipped = 0;
|
||||
for track_id in track_ids.iter().take(20) {
|
||||
match client.get_track(*track_id).await {
|
||||
Ok(track) => {
|
||||
// Check if track's artist is blacklisted
|
||||
if let Some(ref performer) = track.performer {
|
||||
if blacklist_state.is_blacklisted(performer.id) {
|
||||
log::debug!(
|
||||
"[Radio] Skipping blacklisted artist track during refill: {} - {}",
|
||||
performer.name,
|
||||
track.title
|
||||
);
|
||||
blacklist_skipped += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
tracks.push(track);
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -351,6 +402,10 @@ pub async fn refill_radio_queue(
|
||||
}
|
||||
}
|
||||
|
||||
if blacklist_skipped > 0 {
|
||||
log::info!("[Radio] Refill skipped {} tracks from blacklisted artists", blacklist_skipped);
|
||||
}
|
||||
|
||||
let added_count = tracks.len() as u32;
|
||||
|
||||
if tracks.is_empty() {
|
||||
@@ -391,6 +446,7 @@ pub fn get_queue_remaining(state: State<'_, AppState>) -> u32 {
|
||||
pub async fn create_infinite_radio(
|
||||
recent_track_ids: Vec<u64>,
|
||||
state: State<'_, AppState>,
|
||||
blacklist_state: State<'_, BlacklistState>,
|
||||
) -> Result<String, String> {
|
||||
if recent_track_ids.is_empty() {
|
||||
return Err("No recent tracks provided for infinite radio".to_string());
|
||||
@@ -468,11 +524,24 @@ pub async fn create_infinite_radio(
|
||||
.await
|
||||
.map_err(|e| format!("Track generation task failed: {}", e))??;
|
||||
|
||||
// Fetch full track details from Qobuz
|
||||
// Fetch full track details from Qobuz, filtering blacklisted artists
|
||||
let mut tracks = Vec::new();
|
||||
let mut blacklist_skipped = 0;
|
||||
for track_id in track_ids {
|
||||
match client.get_track(track_id).await {
|
||||
Ok(track) => {
|
||||
// Check if track's artist is blacklisted
|
||||
if let Some(ref performer) = track.performer {
|
||||
if blacklist_state.is_blacklisted(performer.id) {
|
||||
log::debug!(
|
||||
"[Radio] Skipping blacklisted artist track in infinite radio: {} - {}",
|
||||
performer.name,
|
||||
track.title
|
||||
);
|
||||
blacklist_skipped += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
tracks.push(track);
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -481,6 +550,10 @@ pub async fn create_infinite_radio(
|
||||
}
|
||||
}
|
||||
|
||||
if blacklist_skipped > 0 {
|
||||
log::info!("[Radio] Infinite radio skipped {} tracks from blacklisted artists", blacklist_skipped);
|
||||
}
|
||||
|
||||
if tracks.is_empty() {
|
||||
return Err("Failed to generate any infinite radio tracks".to_string());
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use tauri::State;
|
||||
|
||||
use crate::api::{endpoints, endpoints::paths, Album, Artist, ArtistAlbums, LabelDetail, Playlist, SearchResultsPage, Track, TracksContainer};
|
||||
use crate::api_cache::ApiCacheState;
|
||||
use crate::artist_blacklist::BlacklistState;
|
||||
use crate::AppState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
@@ -48,12 +49,32 @@ pub async fn search_tracks(
|
||||
offset: Option<u32>,
|
||||
search_type: Option<String>,
|
||||
state: State<'_, AppState>,
|
||||
blacklist_state: State<'_, BlacklistState>,
|
||||
) -> Result<SearchResultsPage<Track>, String> {
|
||||
let client = state.client.lock().await;
|
||||
client
|
||||
let mut results = client
|
||||
.search_tracks(&query, limit.unwrap_or(20), offset.unwrap_or(0), search_type.as_deref())
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Filter out tracks from blacklisted artists
|
||||
let original_count = results.items.len();
|
||||
results.items.retain(|track| {
|
||||
if let Some(ref performer) = track.performer {
|
||||
!blacklist_state.is_blacklisted(performer.id)
|
||||
} else {
|
||||
true // Keep tracks without performer info
|
||||
}
|
||||
});
|
||||
|
||||
let filtered_count = original_count - results.items.len();
|
||||
if filtered_count > 0 {
|
||||
log::debug!("[Blacklist] Filtered {} tracks from search results", filtered_count);
|
||||
// Adjust total to reflect filtered count (approximate)
|
||||
results.total = results.total.saturating_sub(filtered_count as u32);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -63,18 +84,32 @@ pub async fn search_artists(
|
||||
offset: Option<u32>,
|
||||
search_type: Option<String>,
|
||||
state: State<'_, AppState>,
|
||||
blacklist_state: State<'_, BlacklistState>,
|
||||
) -> Result<SearchResultsPage<Artist>, String> {
|
||||
let client = state.client.lock().await;
|
||||
client
|
||||
let mut results = client
|
||||
.search_artists(&query, limit.unwrap_or(20), offset.unwrap_or(0), search_type.as_deref())
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Filter out blacklisted artists
|
||||
let original_count = results.items.len();
|
||||
results.items.retain(|artist| !blacklist_state.is_blacklisted(artist.id));
|
||||
|
||||
let filtered_count = original_count - results.items.len();
|
||||
if filtered_count > 0 {
|
||||
log::debug!("[Blacklist] Filtered {} artists from search results", filtered_count);
|
||||
results.total = results.total.saturating_sub(filtered_count as u32);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn search_all(
|
||||
query: String,
|
||||
state: State<'_, AppState>,
|
||||
blacklist_state: State<'_, BlacklistState>,
|
||||
) -> Result<SearchAllResults, String> {
|
||||
log::debug!("search_all called with query: {}", query);
|
||||
let client = state.client.lock().await;
|
||||
@@ -106,13 +141,13 @@ pub async fn search_all(
|
||||
.unwrap_or_else(|| SearchResultsPage { items: vec![], total: 0, offset: 0, limit: 30 });
|
||||
|
||||
// Parse tracks
|
||||
let tracks: SearchResultsPage<Track> = response
|
||||
let mut tracks: SearchResultsPage<Track> = response
|
||||
.get("tracks")
|
||||
.and_then(|v| serde_json::from_value(v.clone()).ok())
|
||||
.unwrap_or_else(|| SearchResultsPage { items: vec![], total: 0, offset: 0, limit: 30 });
|
||||
|
||||
// Parse artists
|
||||
let artists: SearchResultsPage<Artist> = response
|
||||
let mut artists: SearchResultsPage<Artist> = response
|
||||
.get("artists")
|
||||
.and_then(|v| serde_json::from_value(v.clone()).ok())
|
||||
.unwrap_or_else(|| SearchResultsPage { items: vec![], total: 0, offset: 0, limit: 30 });
|
||||
@@ -152,6 +187,64 @@ pub async fn search_all(
|
||||
}
|
||||
});
|
||||
|
||||
// Apply blacklist filtering
|
||||
let mut filtered_tracks = 0;
|
||||
let mut filtered_artists = 0;
|
||||
|
||||
// Filter tracks from blacklisted artists
|
||||
let original_track_count = tracks.items.len();
|
||||
tracks.items.retain(|track| {
|
||||
if let Some(ref performer) = track.performer {
|
||||
!blacklist_state.is_blacklisted(performer.id)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
filtered_tracks = original_track_count - tracks.items.len();
|
||||
if filtered_tracks > 0 {
|
||||
tracks.total = tracks.total.saturating_sub(filtered_tracks as u32);
|
||||
}
|
||||
|
||||
// Filter blacklisted artists
|
||||
let original_artist_count = artists.items.len();
|
||||
artists.items.retain(|artist| !blacklist_state.is_blacklisted(artist.id));
|
||||
filtered_artists = original_artist_count - artists.items.len();
|
||||
if filtered_artists > 0 {
|
||||
artists.total = artists.total.saturating_sub(filtered_artists as u32);
|
||||
}
|
||||
|
||||
// Filter most_popular if it's a blacklisted item
|
||||
let most_popular = most_popular.and_then(|mp| {
|
||||
match &mp {
|
||||
MostPopularItem::Artists(artist) => {
|
||||
if blacklist_state.is_blacklisted(artist.id) {
|
||||
log::debug!("[Blacklist] Filtered most_popular artist: {}", artist.name);
|
||||
None
|
||||
} else {
|
||||
Some(mp)
|
||||
}
|
||||
}
|
||||
MostPopularItem::Tracks(track) => {
|
||||
if let Some(ref performer) = track.performer {
|
||||
if blacklist_state.is_blacklisted(performer.id) {
|
||||
log::debug!("[Blacklist] Filtered most_popular track: {}", track.title);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(mp)
|
||||
}
|
||||
MostPopularItem::Albums(_) => Some(mp), // Albums not filtered by artist blacklist
|
||||
}
|
||||
});
|
||||
|
||||
if filtered_tracks > 0 || filtered_artists > 0 {
|
||||
log::debug!(
|
||||
"[Blacklist] search_all filtered: {} tracks, {} artists",
|
||||
filtered_tracks,
|
||||
filtered_artists
|
||||
);
|
||||
}
|
||||
|
||||
Ok(SearchAllResults {
|
||||
albums,
|
||||
tracks,
|
||||
@@ -365,6 +458,7 @@ pub async fn get_similar_artists(
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
state: State<'_, AppState>,
|
||||
blacklist_state: State<'_, BlacklistState>,
|
||||
) -> Result<SearchResultsPage<Artist>, String> {
|
||||
log::info!(
|
||||
"Command: get_similar_artists {} limit={:?} offset={:?}",
|
||||
@@ -374,10 +468,22 @@ pub async fn get_similar_artists(
|
||||
);
|
||||
|
||||
let client = state.client.lock().await;
|
||||
client
|
||||
let mut results = client
|
||||
.get_similar_artists(artist_id, limit.unwrap_or(5), offset.unwrap_or(0))
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Filter out blacklisted artists
|
||||
let original_count = results.items.len();
|
||||
results.items.retain(|artist| !blacklist_state.is_blacklisted(artist.id));
|
||||
|
||||
let filtered_count = original_count - results.items.len();
|
||||
if filtered_count > 0 {
|
||||
log::debug!("[Blacklist] Filtered {} similar artists", filtered_count);
|
||||
results.total = results.total.saturating_sub(filtered_count as u32);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Get label detail with albums
|
||||
|
||||
Reference in new issue
Block a user