Capture back-face printing images for double-faced cards

Add a back_image_uri column to printings, populated during ingest from
a card's second card_faces image (only present for transform/MDFC/
reversible cards — split/adventure cards share one combined image and
correctly get ""). Plumb it through CardRef across every query path
(search, library, filters, deck contents) via the same printing row,
using a shared deterministic ORDER BY so front/back always come from
one printing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 20:54:55 -05:00
parent 342b8da797
commit 2ea3c56511
8 changed files with 114 additions and 18 deletions

View File

@@ -599,8 +599,8 @@ fn load_printings(conn: &Connection, reader: impl Read, cancel: &CancelToken) ->
{
let mut ins = conn.prepare(
"INSERT OR IGNORE INTO printings
(scryfall_id,oracle_id,set_code,collector_number,finishes,rarity,image_uri)
VALUES (?1,?2,?3,?4,?5,?6,?7)",
(scryfall_id,oracle_id,set_code,collector_number,finishes,rarity,image_uri,back_image_uri)
VALUES (?1,?2,?3,?4,?5,?6,?7,?8)",
)?;
stream_json_array::<_, ScryPrinting, _>(reader, |p| {
if cancel.is_cancelled() {
@@ -621,6 +621,16 @@ fn load_printings(conn: &Connection, reader: impl Read, cancel: &CancelToken) ->
.and_then(|i| i.normal.clone())
})
.unwrap_or_default();
// Only transform/modal-DFC/reversible cards carry per-face images
// with a distinct second face; split/adventure cards share one
// combined top-level image and have no per-face `image_uris`.
let back_image = p
.card_faces
.as_ref()
.and_then(|f| f.get(1))
.and_then(|f| f.image_uris.as_ref())
.and_then(|i| i.normal.clone())
.unwrap_or_default();
ins.execute(params![
p.id,
oracle_id,
@@ -629,6 +639,7 @@ fn load_printings(conn: &Connection, reader: impl Read, cancel: &CancelToken) ->
p.finishes.join(","),
p.rarity,
image,
back_image,
])?;
count += 1;
Ok(())
@@ -785,4 +796,51 @@ mod tests {
.unwrap();
assert_eq!(g as u8, crate::model::G);
}
#[test]
fn captures_back_face_image_for_double_faced_cards() {
// Transform/MDFC cards: two `card_faces`, each with its own image.
let dfc = r#"[
{"id":"p-dfc","oracle_id":"oid-dfc","set":"znr","collector_number":"1",
"rarity":"rare",
"card_faces":[
{"image_uris":{"normal":"http://img/front"}},
{"image_uris":{"normal":"http://img/back"}}
]}
]"#;
// Split card: two `card_faces` for oracle text, but no per-face images —
// the whole card is one combined top-level image.
let split = r#"[
{"id":"p-split","oracle_id":"oid-split","set":"grn","collector_number":"2",
"rarity":"common","image_uris":{"normal":"http://img/split-combined"},
"card_faces":[{"image_uris":null},{"image_uris":null}]}
]"#;
let dir = tempfile::tempdir().unwrap();
let dest = dir.path().join("reference.db");
let conn = open_reference_for_build(&dest).unwrap();
load_printings(&conn, dfc.as_bytes(), &CancelToken::new()).unwrap();
load_printings(&conn, split.as_bytes(), &CancelToken::new()).unwrap();
let (front, back): (String, String) = conn
.query_row(
"SELECT image_uri, back_image_uri FROM printings WHERE scryfall_id='p-dfc'",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(front, "http://img/front");
assert_eq!(back, "http://img/back");
let (front, back): (String, String) = conn
.query_row(
"SELECT image_uri, back_image_uri FROM printings WHERE scryfall_id='p-split'",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(front, "http://img/split-combined");
assert_eq!(back, "", "split cards have no distinct back face");
}
}