Files
OpenFUT-Core/src/services/card_db.rs
T

77 lines
2.8 KiB
Rust

use crate::models::card::CardDefinition;
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::path::Path;
/// In-memory card registry loaded from data/cards/*.json
pub struct CardDb {
pub cards: HashMap<String, CardDefinition>,
}
impl CardDb {
pub fn load(data_dir: &str) -> Result<Self> {
let cards_dir = Path::new(data_dir).join("cards");
let mut cards = HashMap::new();
if !cards_dir.exists() {
tracing::warn!("Card data directory not found: {:?}", cards_dir);
return Ok(Self { cards });
}
for entry in std::fs::read_dir(&cards_dir)
.with_context(|| format!("reading cards dir {:?}", cards_dir))?
{
let entry = entry?;
let path = entry.path();
if path.extension().map(|e| e == "json").unwrap_or(false) {
let content = std::fs::read_to_string(&path)
.with_context(|| format!("reading {:?}", path))?;
let batch: Vec<CardDefinition> = serde_json::from_str(&content)
.with_context(|| format!("parsing {:?}", path))?;
for card in batch {
cards.insert(card.id.clone(), card);
}
}
}
tracing::info!("Loaded {} card definitions", cards.len());
Ok(Self { cards })
}
/// Merge a game's **opt-in development content pack** from
/// `{data_dir}/games/{game}/dev/cards.json` (a single `CardDefinition[]`).
/// This is NOT read by [`CardDb::load`]; it is loaded only when a game is
/// explicitly named in `Config::dev_content_games`, so default content stays
/// untouched. Returns the number of definitions merged. A missing file is an
/// error (opt-in means the pack is expected to exist).
pub fn load_game_dev(&mut self, data_dir: &str, game: &str) -> Result<usize> {
let path = Path::new(data_dir)
.join("games")
.join(game)
.join("dev")
.join("cards.json");
let content = std::fs::read_to_string(&path)
.with_context(|| format!("reading dev content pack {path:?}"))?;
let batch: Vec<CardDefinition> =
serde_json::from_str(&content).with_context(|| format!("parsing {path:?}"))?;
let n = batch.len();
for card in batch {
self.cards.insert(card.id.clone(), card);
}
tracing::info!("Loaded {} dev card definitions for game '{}'", n, game);
Ok(n)
}
pub fn get(&self, id: &str) -> Option<&CardDefinition> {
self.cards.get(id)
}
pub fn all(&self) -> Vec<&CardDefinition> {
self.cards.values().collect()
}
pub fn by_min_overall(&self, min: u8) -> Vec<&CardDefinition> {
self.cards.values().filter(|c| c.overall >= min).collect()
}
}