You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

176 lines
4.7 KiB

use futures::stream::FuturesUnordered;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::io::Write;
use std::path::PathBuf;
use std::{collections::HashMap, fs::File, io::ErrorKind, path::Path};
use tokio_stream::StreamExt;
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
pub struct Config {
pub name: String,
#[serde(default = "default_version")]
pub version: String,
#[serde(default)]
pub authors: Vec<String>,
pub script: String,
pub available_cards: HashMap<String, Card>,
pub default_back: Option<PathBuf>,
pub piles: HashMap<String, Pile>,
pub player_piles: HashMap<String, Pile>,
}
fn default_version() -> String {
"0.0.0".into()
}
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
pub struct Card {
pub image: PathBuf,
pub back_image: Option<PathBuf>,
#[serde(flatten)]
pub other: HashMap<String, serde_json::Value>,
}
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
pub struct Pile {
pub name: String,
#[serde(default)]
pub cards: Vec<String>,
#[serde(default)]
pub face_down: bool,
#[serde(default = "default_visible")]
pub visible: bool,
#[serde(flatten)]
pub other: HashMap<String, serde_json::Value>,
}
fn default_visible() -> bool {
true
}
impl Config {
pub fn load<P: AsRef<std::path::Path> + std::fmt::Debug>(file: P) -> Self {
let s: Config = serde_json::from_reader(std::fs::File::open(&file).unwrap())
.map_err(|e| {
log::error!(
"Malformed game defintion file @ {}",
file.as_ref().display()
);
log::error!("JSON Error: {}", e);
panic!()
})
.unwrap();
if s.default_back.is_none() {
for (name, card) in &s.available_cards {
if card.back_image.is_none() {
panic!("Card {} from game {} can not have a default back if there's not default back", name, s.name)
}
}
}
s.start_image_caching(file.as_ref().parent().unwrap().to_path_buf());
s
}
fn start_image_caching(&self, folder: PathBuf) {
let mut join_handles = FuturesUnordered::new();
if let Some(p) = &self.default_back {
let p = p.clone();
let folder = folder.clone();
join_handles.push(tokio::task::spawn_blocking(|| {
cache_image(&p, folder);
p
}));
}
for Card {
image,
back_image,
other: _,
} in self.available_cards.values()
{
{
let folder = folder.clone();
let p = image.clone();
join_handles.push(tokio::task::spawn_blocking(|| {
cache_image(&p, folder);
p
}));
};
if let Some(back_image) = back_image {
let p = back_image.clone();
let folder = folder.clone();
join_handles.push(tokio::task::spawn_blocking(|| {
cache_image(&p, folder);
p
}));
}
}
tokio::spawn(async move {
let l = join_handles.len();
let mut i = 1;
while let Some(r) = join_handles.next().await {
match r {
Ok(p) => log::info!("[{}/{}] Image {} cached!", i, l, p.display()),
Err(e) => log::error!("[{}/{}] Error chaching image: {}", i, l, e),
}
i += 1;
}
});
}
pub fn reload_cache(&self, folder: PathBuf) {
std::fs::remove_dir_all(folder.join(".cache")).unwrap();
self.start_image_caching(folder);
}
}
fn cache_image<P1: AsRef<Path>, P2: AsRef<Path>>(p: P1, folder: P2) {
let original = folder.as_ref().join(p.as_ref());
let cache_folder = folder.as_ref().join(".cache");
let mut cached = cache_folder.join(p);
// log::info!("Caching {} on {}", original.display(), cached.display());
// log::info!("Creating {}", cache_folder.display());
match std::fs::create_dir(cache_folder) {
Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()), // Ignore if folder already exists
x => x,
}
.unwrap();
cached.set_extension("png");
if cached.exists()
&& cached.metadata().unwrap().modified().unwrap()
> original.metadata().unwrap().modified().unwrap()
{
// Cache is updated, do nothing
log::info!("cache for {} is up to date", original.display());
} else {
// Update cache
// log::info!("Updating cache for: {}", original.display());
let mut face_buf = Vec::new();
image::open(&original)
.unwrap_or_else(|e| panic!("Error loading the image in {:?} ({})", original, e))
.write_to(&mut face_buf, image::ImageOutputFormat::Png)
.unwrap();
match std::fs::create_dir_all(cached.parent().unwrap()) {
Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()), // Ignore if folder already exists
x => x,
}
.unwrap();
File::create(cached).unwrap().write_all(&face_buf).unwrap();
log::info!("Updated cache for: {}", original.display());
}
}
pub fn setup() {
#[cfg(debug_assertions)]
{
if let Ok(e) = std::env::var("CARGO_MANIFEST_DIR") {
std::fs::write(
AsRef::<std::path::Path>::as_ref(&e)
.join("schema")
.join("game-config.json"),
serde_json::to_string_pretty(&schemars::schema_for!(Config)).unwrap(),
)
.unwrap()
}
}
}