use std::fmt;
use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use palette::Srgb;
use crate::colors::Colors;
use crate::config::Config;
use anyhow::{Result, Context};
pub const CACHE_VER: &str = "1.7";
#[derive(Debug, Default)]
pub struct Cache {
pub path: PathBuf,
pub back: PathBuf,
pub cs: PathBuf,
pub palette: PathBuf,
pub name: PathBuf,
}
impl fmt::Display for Cache {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.path.display())
}
}
type CSret = (Vec<Srgb>, Vec<Srgb>, bool);
#[derive(Debug)]
pub enum IsCached {
None,
Backend,
BackendnCS,
BackendnCSnPalette,
}
impl Cache {
pub fn new(file: &Path, c: &Config, cache_path: &Path) -> Result<Self> {
let cachepath = cache_path.join("wallust");
let hash = base36(fnv1a(&std::fs::read(file)?));
let name = cachepath.join(format!("{hash}_{CACHE_VER}"));
fs::create_dir_all(&name).with_context(|| format!("Failed to create {}", cachepath.display()))?;
let th = if c.true_th == 0 { "auto" } else { &c.true_th.to_string() };
let base = cachepath.join(format!("{hash}_{CACHE_VER}"));
let back = c.backend.to_string();
let cs = c.color_space.to_string();
let palet = c.palette.to_string();
Ok(Self {
path: cachepath,
name,
back: base.join(&back),
cs: base.join(format!("{back}_{cs}_{th}")),
palette: base.join(format!("{back}_{cs}_{th}_{palet}")),
})
}
pub fn read_backend(&self) -> Result<Vec<u8>> { read_json(&self.back) }
pub fn read_cs(&self) -> Result<CSret> { read_json(&self.cs) }
pub fn read_palette(&self) -> Result<Colors> { read_json(&self.palette) }
pub fn write_backend(&self, bytes: &[u8]) -> Result<()> { write_json(&self.back, &bytes, &self.to_string(), false) }
pub fn write_cs(&self, colorspaces: &CSret) -> Result<()> { write_json(&self.cs, colorspaces, &self.to_string(), false) }
pub fn write_palette(&self, scheme: &Colors) -> Result<()> { write_json(&self.palette, scheme, &self.to_string(), true) }
pub fn is_cached_all(&self) -> IsCached {
let b = self.back.exists();
let cs = self.cs.exists();
let p = self.palette.exists();
if b && cs && p {
IsCached::BackendnCSnPalette
} else if b && cs {
IsCached::BackendnCS
} else if b {
IsCached::Backend
} else {
IsCached::None
}
}
}
pub fn write_json<P: AsRef<std::path::Path>, T: serde::Serialize>(path: P, value: &T, cachepath: &str, pretty: bool) -> anyhow::Result<()> {
let serde_to_string = if pretty { serde_json::to_string_pretty } else { serde_json::to_string };
Ok(File::create(&path)?
.write_all(
serde_to_string(value)
.with_context(|| format!("Failed to deserilize from the json cached file: '{cachepath}':"))?
.as_bytes()
)?
)
}
fn read_json<P: AsRef<std::path::Path>, T: serde::de::DeserializeOwned>(path: P) -> anyhow::Result<T> {
let path = path.as_ref();
let f = File::open(path).with_context(|| format!("Failed to open cache file '{}'", path.display()))?;
serde_json::from_reader(BufReader::new(f))
.with_context(|| format!("Failed to parse JSON in cache file '{}'", path.display()))
}
pub fn fnv1a(bytes: &[u8]) -> u32 {
let mut hash = 2166136261;
for byte in bytes {
hash ^= *byte as u32;
hash = hash.wrapping_mul(16777619);
}
hash
}
pub fn base36(n: u32) -> String {
let mut n = n;
let mut result = vec![];
loop {
let m = n % 36;
n /= 36;
result.push(std::char::from_digit(m, 36).expect("is between [2; 36]"));
if n == 0 { break; }
}
result.into_iter().rev().collect()
}