Cover repair for MPD
This commit is contained in:
115
src/db.rs
115
src/db.rs
@@ -969,6 +969,46 @@ pub fn albums_without_cover_from_mpd(conn: &Connection) -> Result<Vec<UncachedAl
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
/// A cached MPD-local cover entry keyed by `(artist, album)`.
|
||||
///
|
||||
/// `cover_url` is expected to point to a local file generated by MPD cover
|
||||
/// extraction (`covers/mpd_<hash>.jpg`).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MpdLocalCoverAlbum {
|
||||
pub artist: String,
|
||||
pub album: String,
|
||||
pub cover_url: String,
|
||||
}
|
||||
|
||||
/// Find MPD-sourced albums that currently point to a local MPD cover file.
|
||||
///
|
||||
/// Used by the `repair-mpd-covers` command to revalidate and, if needed,
|
||||
/// re-fetch previously cached covers.
|
||||
///
|
||||
/// We only include rows where:
|
||||
///
|
||||
/// - at least one scrobble came from MPD (`source = 'MPD'`), and
|
||||
/// - `album_cache.cover_url` is a non-NULL path containing `mpd_`.
|
||||
pub fn albums_with_local_mpd_cover(conn: &Connection) -> Result<Vec<MpdLocalCoverAlbum>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT DISTINCT s.artist, s.album, c.cover_url
|
||||
FROM scrobbles s
|
||||
JOIN album_cache c ON s.artist = c.artist AND s.album = c.album
|
||||
WHERE s.source = 'MPD'
|
||||
AND c.cover_url IS NOT NULL
|
||||
AND c.cover_url LIKE '%mpd_%'
|
||||
ORDER BY s.artist, s.album",
|
||||
)?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
Ok(MpdLocalCoverAlbum {
|
||||
artist: row.get(0)?,
|
||||
album: row.get(1)?,
|
||||
cover_url: row.get(2)?,
|
||||
})
|
||||
})?;
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
/// Cached album metadata returned by [`album_cache_meta`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AlbumCacheMeta {
|
||||
@@ -1761,6 +1801,81 @@ mod tests {
|
||||
// set_local_cover
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_albums_with_local_mpd_cover_returns_only_mpd_local_rows() {
|
||||
let conn = open_memory_db().unwrap();
|
||||
|
||||
insert_scrobble(
|
||||
&conn,
|
||||
&NewScrobble {
|
||||
artist: "Deftones".into(),
|
||||
album: "White Pony".into(),
|
||||
title: "Digital Bath".into(),
|
||||
track_duration_secs: Some(291),
|
||||
played_duration_secs: 200,
|
||||
scrobbled_at: today_at("10:00:00"),
|
||||
source: "MPD".into(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
insert_scrobble(
|
||||
&conn,
|
||||
&NewScrobble {
|
||||
artist: "Tourist".into(),
|
||||
album: "Inside Out".into(),
|
||||
title: "Inside Out".into(),
|
||||
track_duration_secs: Some(240),
|
||||
played_duration_secs: 200,
|
||||
scrobbled_at: today_at("10:05:00"),
|
||||
source: "MPD".into(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
insert_scrobble(
|
||||
&conn,
|
||||
&NewScrobble {
|
||||
artist: "Bonobo".into(),
|
||||
album: "Black Sands".into(),
|
||||
title: "Kiara".into(),
|
||||
track_duration_secs: Some(220),
|
||||
played_duration_secs: 200,
|
||||
scrobbled_at: today_at("10:10:00"),
|
||||
source: "Qobuz".into(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
set_local_cover(
|
||||
&conn,
|
||||
"Deftones",
|
||||
"White Pony",
|
||||
"/tmp/scrbblr/covers/mpd_deadbeef.jpg",
|
||||
)
|
||||
.unwrap();
|
||||
set_local_cover(
|
||||
&conn,
|
||||
"Tourist",
|
||||
"Inside Out",
|
||||
"/tmp/scrbblr/covers/itunes_123.jpg",
|
||||
)
|
||||
.unwrap();
|
||||
set_local_cover(
|
||||
&conn,
|
||||
"Bonobo",
|
||||
"Black Sands",
|
||||
"/tmp/scrbblr/covers/mpd_abcdef01.jpg",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let rows = albums_with_local_mpd_cover(&conn).unwrap();
|
||||
assert_eq!(rows.len(), 1);
|
||||
assert_eq!(rows[0].artist, "Deftones");
|
||||
assert_eq!(rows[0].album, "White Pony");
|
||||
assert!(rows[0].cover_url.contains("mpd_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_local_cover_inserts_new_row() {
|
||||
let conn = open_memory_db().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user