Files
mtgc/src-tauri/src/commands.rs

185 lines
5.4 KiB
Rust
Raw Normal View History

//! Tauri commands: the typed IPC surface bridging the web UI to `mtgc-core`.
//!
//! JS passes camelCase argument keys; Tauri maps them to these snake_case params.
use std::path::PathBuf;
use std::sync::Mutex;
use mtgc_core::{CardRef, Deck, DeckEntry, LibraryCard, LibraryFilter};
use serde::Serialize;
use tauri::{AppHandle, Emitter, State};
use crate::state::AppState;
type Db<'a> = State<'a, Mutex<AppState>>;
type Res<T> = Result<T, String>;
/// High-level status for the top bar / first-run gating.
#[derive(Serialize)]
pub struct Status {
pub library_path: String,
pub reference_path: String,
pub reference_ready: bool,
pub card_count: usize,
}
fn status_of(s: &AppState) -> Status {
Status {
library_path: s.library_path.display().to_string(),
reference_path: s.reference_path.display().to_string(),
reference_ready: s.catalog.is_some(),
card_count: s.index.as_ref().map(|i| i.len()).unwrap_or(0),
}
}
#[tauri::command]
pub fn get_status(db: Db) -> Res<Status> {
Ok(status_of(&db.lock().unwrap()))
}
#[tauri::command]
pub fn set_library_path(db: Db, path: String) -> Res<Status> {
let mut s = db.lock().unwrap();
s.set_library_path(PathBuf::from(path));
// Best-effort reopen: the chosen path must stick (and be reported back)
// even if the reference db can't currently be attached (e.g. stale
// schema pending an "Update card data" run) — that's a separate concern
// from which library file is open.
let _ = s.reopen();
Ok(status_of(&s))
}
/// Download Scryfall bulk data and (re)build reference.db, then reopen.
/// Runs on a blocking thread so the UI stays responsive and progress events flow.
#[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();
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())?;
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();
Ok(s.index()?.search(&query, limit))
}
#[tauri::command]
pub fn add_owned(db: Db, oracle_id: String, delta: i64) -> Res<i64> {
let s = db.lock().unwrap();
s.catalog()?.add_owned(&oracle_id, delta).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn set_owned(db: Db, oracle_id: String, quantity: i64) -> Res<i64> {
let s = db.lock().unwrap();
s.catalog()?
.set_owned(&oracle_id, quantity)
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn set_location(db: Db, oracle_id: String, location: String) -> Res<()> {
let s = db.lock().unwrap();
s.catalog()?
.set_location(&oracle_id, &location)
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn deck_contents(db: Db, deck_id: i64) -> Res<Vec<DeckEntry>> {
let s = db.lock().unwrap();
s.catalog()?.deck_contents(deck_id).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn search_library(db: Db, filter: LibraryFilter) -> Res<Vec<LibraryCard>> {
let s = db.lock().unwrap();
s.catalog()?.search_library(&filter).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn list_decks(db: Db) -> Res<Vec<Deck>> {
let s = db.lock().unwrap();
s.catalog()?.list_decks().map_err(|e| e.to_string())
}
#[tauri::command]
pub fn create_deck(db: Db, name: String, commander_oracle_id: Option<String>) -> Res<Deck> {
let s = db.lock().unwrap();
s.catalog()?
.create_deck(&name, commander_oracle_id.as_deref())
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn delete_deck(db: Db, deck_id: i64) -> Res<()> {
let s = db.lock().unwrap();
s.catalog()?.delete_deck(deck_id).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn set_deck_format(db: Db, deck_id: i64, format: String) -> Res<()> {
let s = db.lock().unwrap();
s.catalog()?
.set_deck_format(deck_id, &format)
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn set_deck_commander(db: Db, deck_id: i64, oracle_id: Option<String>) -> Res<()> {
let s = db.lock().unwrap();
s.catalog()?
.set_deck_commander(deck_id, oracle_id.as_deref())
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn set_deck_card(
db: Db,
deck_id: i64,
oracle_id: String,
quantity: i64,
is_commander: bool,
) -> Res<()> {
let s = db.lock().unwrap();
s.catalog()?
.set_deck_card(deck_id, &oracle_id, quantity, is_commander)
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn card_decks(db: Db, oracle_id: String) -> Res<Vec<Deck>> {
let s = db.lock().unwrap();
s.catalog()?.card_decks(&oracle_id).map_err(|e| e.to_string())
}