Add source/qobuz_track_id to LocalTrack model and implement filtering for Artists/Tracks

- Added source and qobuz_track_id fields to LocalTrack model for proper filtering
- Updated all database row mapping functions to include new fields
- Implemented network folder and download filtering for library_get_artists command
- Implemented network folder and download filtering for library_search (Tracks) command
- Made tab counters (Albums, Artists, Tracks) reactive to filtered content
- Counters now update based on current filtered data instead of total stats

Fixes offline mode filtering for Artists and Tracks tabs. When offline or when
downloads are hidden, Artists/Tracks now properly filter out inaccessible content,
matching the behavior already implemented for Albums.
This commit is contained in:
vicrodh committed 2026-01-20 01:26:27 -06:00
1 parent 1347c11fcc
commit 2270d80139
6 files changed
+152 -14

No files matched your search

+98 -6
View File
@@ -848,24 +848,116 @@ pub async fn library_get_album_tracks(
}
#[tauri::command]
pub async fn library_get_artists(state: State<'_, LibraryState>) -> Result<Vec<LocalArtist>, String> {
log::info!("Command: library_get_artists");
pub async fn library_get_artists(
exclude_network_folders: Option<bool>,
state: State<'_, LibraryState>,
download_settings_state: State<'_, crate::config::DownloadSettingsState>,
) -> Result<Vec<LocalArtist>, String> {
log::info!("Command: library_get_artists (exclude_network: {:?})", exclude_network_folders);
// Get download settings
let include_qobuz = download_settings_state
.lock()
.map_err(|e| format!("Failed to lock download settings: {}", e))?
.get_settings()
.map(|s| s.show_in_library)
.unwrap_or(false);
let db = state.db.lock().await;
db.get_artists().map_err(|e| e.to_string())
let mut artists = db.get_artists().map_err(|e| e.to_string())?;
// Filter artists based on their tracks
if exclude_network_folders.unwrap_or(false) || !include_qobuz {
let network_folders = if exclude_network_folders.unwrap_or(false) {
db.get_network_folder_paths().map_err(|e| e.to_string())?
} else {
Vec::new()
};
artists.retain(|artist| {
// Get all tracks for this artist
if let Ok(tracks) = db.search(&artist.name, 10000) {
// Check if artist has at least one valid track
tracks.iter().any(|track| {
// Check download filter
let passes_download_filter = if include_qobuz {
true
} else {
// Only include if NOT a qobuz download
track.source.as_deref() != Some("qobuz_download")
};
// Check network folder filter
let passes_network_filter = if exclude_network_folders.unwrap_or(false) && !network_folders.is_empty() {
// Only include if NOT in network folder
!network_folders.iter().any(|folder_path| track.file_path.starts_with(folder_path))
} else {
true
};
passes_download_filter && passes_network_filter
})
} else {
false
}
});
}
Ok(artists)
}
#[tauri::command]
pub async fn library_search(
query: String,
limit: Option<u32>,
exclude_network_folders: Option<bool>,
state: State<'_, LibraryState>,
download_settings_state: State<'_, crate::config::DownloadSettingsState>,
) -> Result<Vec<LocalTrack>, String> {
log::info!("Command: library_search \"{}\"", query);
log::info!("Command: library_search \"{}\" (exclude_network: {:?})", query, exclude_network_folders);
// Get download settings
let include_qobuz = download_settings_state
.lock()
.map_err(|e| format!("Failed to lock download settings: {}", e))?
.get_settings()
.map(|s| s.show_in_library)
.unwrap_or(false);
let db = state.db.lock().await;
db.search(&query, limit.unwrap_or(50))
.map_err(|e| e.to_string())
let mut tracks = db.search(&query, limit.unwrap_or(50))
.map_err(|e| e.to_string())?;
// Filter tracks
if exclude_network_folders.unwrap_or(false) || !include_qobuz {
let network_folders = if exclude_network_folders.unwrap_or(false) {
db.get_network_folder_paths().map_err(|e| e.to_string())?
} else {
Vec::new()
};
tracks.retain(|track| {
// Check download filter
let passes_download_filter = if include_qobuz {
true
} else {
// Only include if NOT a qobuz download
track.source.as_deref() != Some("qobuz_download")
};
// Check network folder filter
let passes_network_filter = if exclude_network_folders.unwrap_or(false) && !network_folders.is_empty() {
// Only include if NOT in network folder
!network_folders.iter().any(|folder_path| track.file_path.starts_with(folder_path))
} else {
true
};
passes_download_filter && passes_network_filter
});
}
Ok(tracks)
}
#[tauri::command]
+2
View File
@@ -270,6 +270,8 @@ pub fn cue_to_tracks(
artwork_path: None,
last_modified: 0,
indexed_at: now,
source: None,
qobuz_track_id: None,
});
}
+11 -3
View File
@@ -1218,6 +1218,8 @@ impl LibraryDatabase {
artwork_path: row.get(19)?,
last_modified: row.get(20)?,
indexed_at: row.get(21)?,
source: row.get(24).ok().flatten(),
qobuz_track_id: row.get(25).ok().flatten(),
})
}
@@ -1788,7 +1790,8 @@ impl LibraryDatabase {
t.album_group_key, t.album_group_title, t.track_number, t.disc_number,
t.year, t.genre, t.duration_secs, t.format, t.bit_depth, t.sample_rate,
t.channels, t.file_size_bytes, t.cue_file_path, t.cue_start_secs,
t.cue_end_secs, t.artwork_path, t.last_modified, t.indexed_at, plt.position
t.cue_end_secs, t.artwork_path, t.last_modified, t.indexed_at, t.source,
t.qobuz_track_id, plt.position
FROM playlist_local_tracks plt
JOIN local_tracks t ON plt.local_track_id = t.id
WHERE plt.qobuz_playlist_id = ?1
@@ -1822,6 +1825,8 @@ impl LibraryDatabase {
artwork_path: row.get(21)?,
last_modified: row.get(22)?,
indexed_at: row.get(23)?,
source: row.get(24)?,
qobuz_track_id: row.get(25)?,
})
}).map_err(|e| LibraryError::Database(format!("Failed to query playlist local tracks: {}", e)))?;
@@ -1836,7 +1841,8 @@ impl LibraryDatabase {
t.album_group_key, t.album_group_title, t.track_number, t.disc_number,
t.year, t.genre, t.duration_secs, t.format, t.bit_depth, t.sample_rate,
t.channels, t.file_size_bytes, t.cue_file_path, t.cue_start_secs,
t.cue_end_secs, t.artwork_path, t.last_modified, t.indexed_at, plt.position
t.cue_end_secs, t.artwork_path, t.last_modified, t.indexed_at, t.source,
t.qobuz_track_id, plt.position
FROM playlist_local_tracks plt
JOIN local_tracks t ON plt.local_track_id = t.id
WHERE plt.qobuz_playlist_id = ?1
@@ -1871,8 +1877,10 @@ impl LibraryDatabase {
artwork_path: row.get(21)?,
last_modified: row.get(22)?,
indexed_at: row.get(23)?,
source: row.get(24)?,
qobuz_track_id: row.get(25)?,
},
playlist_position: row.get(24)?,
playlist_position: row.get(26)?,
})
}).map_err(|e| LibraryError::Database(format!("Failed to query playlist local tracks with position: {}", e)))?;
+4
View File
@@ -341,6 +341,8 @@ impl MetadataExtractor {
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0),
source: None,
qobuz_track_id: None,
}
} else {
// No tag found, use defaults
@@ -380,6 +382,8 @@ impl MetadataExtractor {
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0),
source: None,
qobuz_track_id: None,
}
};
+6
View File
@@ -72,6 +72,10 @@ pub struct LocalTrack {
// Indexing
pub last_modified: i64,
pub indexed_at: i64,
// Download tracking
pub source: Option<String>,
pub qobuz_track_id: Option<i64>,
}
impl Default for LocalTrack {
@@ -102,6 +106,8 @@ impl Default for LocalTrack {
artwork_path: None,
last_modified: 0,
indexed_at: 0,
source: None,
qobuz_track_id: None,
}
}
}