feat: add downloaded purchases registry in SQLite backend with V2 commands

- downloaded_purchases table in library DB (permanent, per-user)
- V2 commands: mark_downloaded, remove_downloaded, get_downloaded_track_ids
- Frontend enriches purchase data with download status from backend
- Grid overlay for unavailable albums centered on artwork
- Quality filter expanded: All / Hi-Res / CD / Lossy (radio group)
- Album download status derived from track-level registry
This commit is contained in:
vicrodh committed 2026-02-17 18:57:24 -06:00
1 parent eb6830430e
commit 42915197e3
7 files changed
+236 -38

No files matched your search

+64
View File
@@ -191,6 +191,17 @@ impl LibraryDatabase {
);
CREATE INDEX IF NOT EXISTS idx_artist_images_fetched ON artist_images(fetched_at);
-- Downloaded purchases registry (permanent — user owns these files)
CREATE TABLE IF NOT EXISTS downloaded_purchases (
track_id INTEGER PRIMARY KEY,
album_id TEXT,
file_path TEXT NOT NULL,
downloaded_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_downloaded_purchases_album
ON downloaded_purchases(album_id);
"#,
)
.map_err(|e| LibraryError::Database(format!("Failed to create schema: {}", e)))?;
@@ -3726,4 +3737,57 @@ impl LibraryDatabase {
.collect::<Result<Vec<_>, _>>()
.map_err(|e| LibraryError::Database(format!("Failed to collect playlists: {}", e)))
}
// ── Downloaded Purchases Registry ──
/// Record a track as downloaded on this computer.
pub fn mark_purchase_downloaded(
&self,
track_id: i64,
album_id: Option<&str>,
file_path: &str,
) -> Result<(), LibraryError> {
self.conn
.execute(
"INSERT OR REPLACE INTO downloaded_purchases (track_id, album_id, file_path, downloaded_at)
VALUES (?1, ?2, ?3, datetime('now'))",
rusqlite::params![track_id, album_id, file_path],
)
.map_err(|e| {
LibraryError::Database(format!("Failed to mark purchase downloaded: {}", e))
})?;
Ok(())
}
/// Remove a downloaded purchase record (e.g. user deleted the file).
pub fn remove_downloaded_purchase(&self, track_id: i64) -> Result<(), LibraryError> {
self.conn
.execute(
"DELETE FROM downloaded_purchases WHERE track_id = ?1",
[track_id],
)
.map_err(|e| {
LibraryError::Database(format!("Failed to remove downloaded purchase: {}", e))
})?;
Ok(())
}
/// Get all downloaded track IDs for fast lookup.
pub fn get_downloaded_purchase_track_ids(&self) -> Result<Vec<i64>, LibraryError> {
let mut stmt = self
.conn
.prepare("SELECT track_id FROM downloaded_purchases")
.map_err(|e| {
LibraryError::Database(format!("Failed to prepare statement: {}", e))
})?;
let ids = stmt
.query_map([], |row| row.get::<_, i64>(0))
.map_err(|e| {
LibraryError::Database(format!("Failed to query downloaded purchases: {}", e))
})?;
ids.collect::<Result<Vec<_>, _>>()
.map_err(|e| LibraryError::Database(format!("Failed to collect ids: {}", e)))
}
}