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
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()
|
|
}
|
|
}
|
|
}
|
|
|