fix(library): detect qobuz_purchase source on scan and clean stale downloads

insert_track() now checks downloaded_purchases table to set source.
get_downloaded_purchase_track_ids() verifies file existence and
auto-removes stale entries. Closes #115
This commit is contained in:
vicrodh committed 2026-03-05 11:13:59 -06:00
1 parent fa747ecf8b
commit 87321a0ea6
1 file changed
+49 -9
+49 -9
View File
@@ -990,6 +990,17 @@ impl LibraryDatabase {
.map_err(|e| LibraryError::Database(e.to_string()));
}
// Detect if this file is a Qobuz purchased download
let is_purchase: bool = self.conn
.query_row(
"SELECT COUNT(*) FROM downloaded_purchases WHERE file_path = ?1",
params![track.file_path],
|row| row.get::<_, i64>(0),
)
.unwrap_or(0) > 0;
let source = if is_purchase { "qobuz_purchase" } else { "user" };
self.conn
.execute(
r#"INSERT OR REPLACE INTO local_tracks
@@ -997,8 +1008,8 @@ impl LibraryDatabase {
disc_number, year, genre, catalog_number, duration_secs, format, bit_depth,
sample_rate, channels, file_size_bytes, cue_file_path,
cue_start_secs, cue_end_secs, artwork_path, last_modified, indexed_at,
album_group_key, album_group_title)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"#,
album_group_key, album_group_title, source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"#,
params![
track.file_path,
track.title,
@@ -1023,7 +1034,8 @@ impl LibraryDatabase {
track.last_modified,
track.indexed_at,
track.album_group_key,
track.album_group_title
track.album_group_title,
source
],
)
.map_err(|e| LibraryError::Database(e.to_string()))?;
@@ -3946,22 +3958,50 @@ impl LibraryDatabase {
}
/// Get all downloaded track IDs for fast lookup (any format).
/// Automatically removes stale entries where the file no longer exists on disk.
pub fn get_downloaded_purchase_track_ids(&self) -> Result<Vec<i64>, LibraryError> {
let mut stmt = self
.conn
.prepare("SELECT DISTINCT track_id FROM downloaded_purchases")
.prepare("SELECT track_id, format_id, file_path FROM downloaded_purchases")
.map_err(|e| {
LibraryError::Database(format!("Failed to prepare statement: {}", e))
})?;
let ids = stmt
.query_map([], |row| row.get::<_, i64>(0))
let rows: Vec<(i64, i64, String)> = stmt
.query_map([], |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
})
.map_err(|e| {
LibraryError::Database(format!("Failed to query downloaded purchases: {}", e))
})?;
})?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| LibraryError::Database(format!("Failed to collect rows: {}", e)))?;
ids.collect::<Result<Vec<_>, _>>()
.map_err(|e| LibraryError::Database(format!("Failed to collect ids: {}", e)))
let mut stale: Vec<(i64, i64)> = Vec::new();
let mut valid_ids: Vec<i64> = Vec::new();
for (track_id, format_id, file_path) in &rows {
if std::path::Path::new(file_path).exists() {
valid_ids.push(*track_id);
} else {
stale.push((*track_id, *format_id));
}
}
// Remove stale entries where the file no longer exists
if !stale.is_empty() {
log::info!("Removing {} stale downloaded_purchases entries (files deleted)", stale.len());
for (track_id, format_id) in &stale {
let _ = self.conn.execute(
"DELETE FROM downloaded_purchases WHERE track_id = ?1 AND format_id = ?2",
rusqlite::params![track_id, format_id],
);
}
}
valid_ids.sort_unstable();
valid_ids.dedup();
Ok(valid_ids)
}
/// Get all downloaded (track_id, format_id) pairs for building per-format lookup.