1use std::fmt;
3use std::fs;
4use std::fs::File;
5use std::io::BufReader;
6use std::io::Write;
7use std::path::Path;
8use std::path::PathBuf;
9use palette::Srgb;
10
11use crate::colors::Colors;
12use crate::config::Config;
13
14use anyhow::{Result, Context};
15
16pub const CACHE_VER: &str = "1.7";
21
22#[derive(Debug, Default)]
24pub struct Cache {
25 pub path: PathBuf,
27 pub back: PathBuf,
29 pub cs: PathBuf,
31 pub palette: PathBuf,
33
34 pub name: PathBuf,
36}
37
38impl fmt::Display for Cache {
40 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41 write!(f, "{}", self.path.display())
42 }
43}
44
45type CSret = (Vec<Srgb>, Vec<Srgb>, bool);
47
48#[derive(Debug)]
50pub enum IsCached {
51 None,
52 Backend,
53 BackendnCS,
54 BackendnCSnPalette,
55}
56
57impl Cache {
58 pub fn new(file: &Path, c: &Config, cache_path: &Path) -> Result<Self> {
65 let cachepath = cache_path.join("wallust");
67
68 let hash = base36(fnv1a(&std::fs::read(file)?));
70
71 let name = cachepath.join(format!("{hash}_{CACHE_VER}"));
72 fs::create_dir_all(&name).with_context(|| format!("Failed to create {}", cachepath.display()))?;
74
75 let th = if c.true_th == 0 { "auto" } else { &c.true_th.to_string() };
76 let base = cachepath.join(format!("{hash}_{CACHE_VER}"));
78
79 let back = c.backend.to_string();
80 let cs = c.color_space.to_string();
81 let palet = c.palette.to_string();
82
83 Ok(Self {
84 path: cachepath,
85 name,
86 back: base.join(&back),
87 cs: base.join(format!("{back}_{cs}_{th}")),
88 palette: base.join(format!("{back}_{cs}_{th}_{palet}")),
89 })
90 }
91
92 pub fn read_backend(&self) -> Result<Vec<u8>> { read_json(&self.back) }
93 pub fn read_cs(&self) -> Result<CSret> { read_json(&self.cs) }
94 pub fn read_palette(&self) -> Result<Colors> { read_json(&self.palette) }
95 pub fn write_backend(&self, bytes: &[u8]) -> Result<()> { write_json(&self.back, &bytes, &self.to_string(), false) }
96 pub fn write_cs(&self, colorspaces: &CSret) -> Result<()> { write_json(&self.cs, colorspaces, &self.to_string(), false) }
97 pub fn write_palette(&self, scheme: &Colors) -> Result<()> { write_json(&self.palette, scheme, &self.to_string(), true) }
98
99 pub fn is_cached_all(&self) -> IsCached {
100 let b = self.back.exists();
101 let cs = self.cs.exists();
102 let p = self.palette.exists();
103
104 if b && cs && p {
105 IsCached::BackendnCSnPalette
106 } else if b && cs {
107 IsCached::BackendnCS
108 } else if b {
109 IsCached::Backend
110 } else {
111 IsCached::None
112 }
113 }
114}
115
116pub fn write_json<P: AsRef<std::path::Path>, T: serde::Serialize>(path: P, value: &T, cachepath: &str, pretty: bool) -> anyhow::Result<()> {
120 let serde_to_string = if pretty { serde_json::to_string_pretty } else { serde_json::to_string };
121 Ok(File::create(&path)?
122 .write_all(
123 serde_to_string(value)
124 .with_context(|| format!("Failed to deserilize from the json cached file: '{cachepath}':"))?
125 .as_bytes()
126 )?
127 )
128}
129
130fn read_json<P: AsRef<std::path::Path>, T: serde::de::DeserializeOwned>(path: P) -> anyhow::Result<T> {
131 let path = path.as_ref();
132 let f = File::open(path).with_context(|| format!("Failed to open cache file '{}'", path.display()))?;
133 serde_json::from_reader(BufReader::new(f))
134 .with_context(|| format!("Failed to parse JSON in cache file '{}'", path.display()))
135}
136
137
138pub fn fnv1a(bytes: &[u8]) -> u32 {
144 let mut hash = 2166136261;
145
146 for byte in bytes {
147 hash ^= *byte as u32;
148 hash = hash.wrapping_mul(16777619);
149 }
150
151 hash
152}
153
154pub fn base36(n: u32) -> String {
159 let mut n = n;
160 let mut result = vec![];
161
162 loop {
163 let m = n % 36;
164 n /= 36;
165 result.push(std::char::from_digit(m, 36).expect("is between [2; 36]"));
166 if n == 0 { break; }
167 }
168 result.into_iter().rev().collect()
169}