Overhaul card-data ingest: filter art cards, segmented cancellable progress

- Exclude Scryfall Art Series cards (layout "art_series") from ingest —
  they aren't playable cards and were polluting search results.
- Replace the coarse per-stage progress text with a byte-accurate,
  weighted multi-segment progress bar (Index / Card data / Printings /
  Indexing) that fills as one continuous bar instead of swapping per
  stage; segment weights are computed from the real download sizes
  reported by Scryfall's bulk-data index (the download responses
  themselves are chunked with no Content-Length to rely on).
- Add a Cancel button, wired through a new CancelToken checked once per
  parsed record so cancellation lands almost instantly.
- Fix a related correctness bug this surfaced: ingest used to delete the
  existing reference.db before writing its replacement, so a cancelled
  or crashed update left the app with no card database at all. It now
  builds into a temp file and only atomically replaces reference.db on
  full success.
- Split the topbar's single "Choose library…" action into separate
  "Open Library…" (existing file) and "New Library…" (save-dialog for a
  new file) buttons.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 20:22:31 -05:00
parent 0eaa41ec41
commit 342b8da797
9 changed files with 623 additions and 48 deletions

View File

@@ -50,22 +50,39 @@ pub fn set_library_path(db: Db, path: String) -> Res<Status> {
#[tauri::command]
pub async fn update_card_data(app: AppHandle, db: Db<'_>) -> Res<Status> {
let reference_path = db.lock().unwrap().reference_path.clone();
let cancel = mtgc_core::ingest::CancelToken::new();
db.lock().unwrap().ingest_cancel = Some(cancel.clone());
let app_for_progress = app.clone();
tauri::async_runtime::spawn_blocking(move || {
mtgc_core::ingest::build_reference_db(&reference_path, |msg| {
let _ = app_for_progress.emit("ingest-progress", msg.to_string());
})
let result = tauri::async_runtime::spawn_blocking({
let cancel = cancel.clone();
move || {
mtgc_core::ingest::build_reference_db(&reference_path, &cancel, |p| {
let _ = app_for_progress.emit("ingest-progress", p);
})
}
})
.await
.map_err(|e| e.to_string())?
.map_err(|e| e.to_string())?;
db.lock().unwrap().ingest_cancel = None;
result.map_err(|e| e.to_string())?;
let mut s = db.lock().unwrap();
s.reopen()?;
Ok(status_of(&s))
}
/// Cancel an in-progress `update_card_data`, if one is running. No-op otherwise.
#[tauri::command]
pub fn cancel_update_card_data(db: Db) -> Res<()> {
let s = db.lock().unwrap();
if let Some(c) = &s.ingest_cancel {
c.cancel();
}
Ok(())
}
#[tauri::command]
pub fn search_cards(db: Db, query: String, limit: usize) -> Res<Vec<CardRef>> {
let s = db.lock().unwrap();