duat_core/context/
cache.rs1use std::{
31 ffi::OsString,
32 fs::File,
33 hash::{DefaultHasher, Hash, Hasher},
34 marker::PhantomData,
35 path::{Path, PathBuf},
36};
37
38pub use bincode::{self, Decode, Encode};
39
40use self::bincode::{
41 config::{Configuration, Fixint, LittleEndian, NoLimit},
42 encode_into_std_write,
43};
44use crate::{
45 text::{Text, txt},
46 utils::{duat_name, src_crate},
47};
48
49pub struct Cache(PhantomData<()>);
51
52impl Cache {
53 pub(crate) fn new() -> Self {
55 Self(PhantomData)
56 }
57
58 pub fn load<C: Decode<()> + Default + 'static>(
64 &self,
65 path: impl AsRef<Path>,
66 ) -> Result<C, Text> {
67 let mut cache_file = cache_file::<C>(path.as_ref(), false)?;
68
69 if cache_file.metadata()?.len() == 0 {
70 return Ok(C::default());
71 }
72
73 let config = Configuration::<LittleEndian, Fixint, NoLimit>::default();
74 bincode::decode_from_std_read(&mut cache_file, config).map_err(|err| txt!("{err}"))
75 }
76
77 pub fn store<C: Encode + 'static>(
83 &self,
84 path: impl AsRef<Path>,
85 cache: C,
86 ) -> Result<usize, Text> {
87 let mut cache_file = cache_file::<C>(path.as_ref(), true)?;
88
89 let config = Configuration::<LittleEndian, Fixint, NoLimit>::default();
90 encode_into_std_write(cache, &mut cache_file, config).map_err(|err| txt!("{err}"))
91 }
92
93 pub fn delete(&self, path: impl Into<PathBuf>) {
98 fn delete_cache_inner(path: PathBuf) {
99 let (Some(cache_dir), Some(file_name)) = (dirs_next::cache_dir(), path.file_name())
100 else {
101 return;
102 };
103
104 let mut hasher = DefaultHasher::new();
105 path.hash(&mut hasher);
106 let hash_value = hasher.finish();
107
108 let cached_file_name = {
109 let mut name = OsString::from(format!("{hash_value}-"));
110 name.push(file_name);
111 name
112 };
113
114 let src = cache_dir
115 .join("duat")
116 .join("structs")
117 .join(cached_file_name);
118 let _ = std::fs::remove_dir_all(src);
121 }
122
123 delete_cache_inner(path.into());
124 }
125
126 pub fn delete_for<C: 'static>(&self, path: impl AsRef<Path>) {
128 let path = path.as_ref();
129 let (Some(cache_dir), Some(file_name)) = (dirs_next::cache_dir(), path.file_name()) else {
130 return;
131 };
132
133 let mut hasher = DefaultHasher::new();
134 path.hash(&mut hasher);
135 let hash_value = hasher.finish();
136
137 let cached_file_name = {
138 let mut name = OsString::from(format!("{hash_value}-"));
139 name.push(file_name);
140 name
141 };
142
143 let src = cache_dir
144 .join("duat")
145 .join("structs")
146 .join(cached_file_name)
147 .join(format!("{}-{}", src_crate::<C>(), duat_name::<C>()));
148
149 if let Ok(true) = src.try_exists() {
150 std::fs::remove_file(src).unwrap();
151 }
152 }
153}
154
155fn cache_file<C: 'static>(path: &Path, truncate: bool) -> std::io::Result<File> {
156 let mut hasher = DefaultHasher::new();
157 path.hash(&mut hasher);
158 let hash_value = hasher.finish();
159
160 let cached_file_name = {
161 let mut name = OsString::from(format!("{hash_value}-"));
162 name.push(path.file_name().ok_or_else(|| {
163 std::io::Error::new(
164 std::io::ErrorKind::IsADirectory,
165 format!("{path:?} is a directory, not a buffer for caching"),
166 )
167 })?);
168 name
169 };
170
171 let src_dir = dirs_next::cache_dir()
172 .ok_or_else(|| {
173 std::io::Error::new(std::io::ErrorKind::NotFound, "cache directory isn't set")
174 })?
175 .join("duat")
176 .join("structs")
177 .join(cached_file_name.clone());
178
179 if !src_dir.exists() {
180 std::fs::create_dir_all(src_dir.clone())?;
181 }
182
183 let src = src_dir.join(format!("{}-{}", src_crate::<C>(), duat_name::<C>()));
184
185 std::fs::OpenOptions::new()
186 .create(true)
187 .read(true)
188 .write(true)
189 .truncate(truncate)
190 .open(src)
191}