1use std::collections::BTreeMap;
10use std::fs::{self, File, OpenOptions};
11use std::io::{self, Write};
12use std::path::{Component, Path, PathBuf};
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::{Arc, Mutex};
15
16pub trait TileStore {
18 fn get(&self, path: &str) -> io::Result<Option<Vec<u8>>>;
20 fn put(&self, path: &str, bytes: &[u8]) -> io::Result<()>;
22 fn create_new(&self, path: &str, bytes: &[u8]) -> io::Result<bool>;
25 fn delete(&self, path: &str) -> io::Result<bool>;
27 fn rename(&self, from: &str, to: &str) -> io::Result<()>;
29 fn list(&self, prefix: &str) -> io::Result<Vec<String>>;
32 fn exists(&self, path: &str) -> io::Result<bool> {
34 Ok(self.get(path)?.is_some())
35 }
36}
37
38impl<T: TileStore + ?Sized> TileStore for &T {
39 fn get(&self, path: &str) -> io::Result<Option<Vec<u8>>> {
40 (**self).get(path)
41 }
42 fn put(&self, path: &str, bytes: &[u8]) -> io::Result<()> {
43 (**self).put(path, bytes)
44 }
45 fn create_new(&self, path: &str, bytes: &[u8]) -> io::Result<bool> {
46 (**self).create_new(path, bytes)
47 }
48 fn delete(&self, path: &str) -> io::Result<bool> {
49 (**self).delete(path)
50 }
51 fn rename(&self, from: &str, to: &str) -> io::Result<()> {
52 (**self).rename(from, to)
53 }
54 fn list(&self, prefix: &str) -> io::Result<Vec<String>> {
55 (**self).list(prefix)
56 }
57 fn exists(&self, path: &str) -> io::Result<bool> {
58 (**self).exists(path)
59 }
60}
61
62impl<T: TileStore + ?Sized> TileStore for Arc<T> {
63 fn get(&self, path: &str) -> io::Result<Option<Vec<u8>>> {
64 (**self).get(path)
65 }
66 fn put(&self, path: &str, bytes: &[u8]) -> io::Result<()> {
67 (**self).put(path, bytes)
68 }
69 fn create_new(&self, path: &str, bytes: &[u8]) -> io::Result<bool> {
70 (**self).create_new(path, bytes)
71 }
72 fn delete(&self, path: &str) -> io::Result<bool> {
73 (**self).delete(path)
74 }
75 fn rename(&self, from: &str, to: &str) -> io::Result<()> {
76 (**self).rename(from, to)
77 }
78 fn list(&self, prefix: &str) -> io::Result<Vec<String>> {
79 (**self).list(prefix)
80 }
81 fn exists(&self, path: &str) -> io::Result<bool> {
82 (**self).exists(path)
83 }
84}
85
86#[derive(Debug, Clone)]
89pub struct PosixStore {
90 root: PathBuf,
91 counter: Arc<AtomicU64>,
92}
93
94impl PosixStore {
95 pub fn open(root: impl Into<PathBuf>) -> io::Result<Self> {
97 let root = root.into();
98 fs::create_dir_all(&root)?;
99 fs::create_dir_all(root.join(".tmp"))?;
100 Ok(Self {
101 root,
102 counter: Arc::new(AtomicU64::new(0)),
103 })
104 }
105
106 #[must_use]
108 pub fn root(&self) -> &Path {
109 &self.root
110 }
111
112 fn resolve(&self, path: &str) -> io::Result<PathBuf> {
113 if path.is_empty() {
114 return Ok(self.root.clone());
115 }
116 let relative = Path::new(path);
117 if relative
118 .components()
119 .any(|c| !matches!(c, Component::Normal(_)))
120 {
121 return Err(io::Error::new(
122 io::ErrorKind::InvalidInput,
123 format!("store path {path:?} leaves the store"),
124 ));
125 }
126 Ok(self.root.join(relative))
127 }
128
129 fn write_temp(&self, bytes: &[u8]) -> io::Result<PathBuf> {
130 let n = self.counter.fetch_add(1, Ordering::Relaxed);
131 let name = format!("{}-{n}", std::process::id());
132 let temp = self.root.join(".tmp").join(name);
133 let mut file = File::create(&temp)?;
134 file.write_all(bytes)?;
135 file.sync_all()?;
136 Ok(temp)
137 }
138
139 fn sync_dir(dir: &Path) -> io::Result<()> {
140 File::open(dir)?.sync_all()
141 }
142
143 fn walk(dir: &Path, root: &Path, out: &mut Vec<String>) -> io::Result<()> {
144 for entry in fs::read_dir(dir)? {
145 let entry = entry?;
146 let path = entry.path();
147 if entry.file_type()?.is_dir() {
148 Self::walk(&path, root, out)?;
149 } else {
150 let relative = path.strip_prefix(root).map_err(io::Error::other)?;
151 let text = relative
152 .components()
153 .map(|c| c.as_os_str().to_string_lossy().into_owned())
154 .collect::<Vec<_>>()
155 .join("/");
156 out.push(text);
157 }
158 }
159 Ok(())
160 }
161}
162
163impl TileStore for PosixStore {
164 fn get(&self, path: &str) -> io::Result<Option<Vec<u8>>> {
165 match fs::read(self.resolve(path)?) {
166 Ok(bytes) => Ok(Some(bytes)),
167 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
168 Err(e) => Err(e),
169 }
170 }
171
172 fn put(&self, path: &str, bytes: &[u8]) -> io::Result<()> {
173 let target = self.resolve(path)?;
174 let parent = target
175 .parent()
176 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "no parent"))?;
177 fs::create_dir_all(parent)?;
178 let temp = self.write_temp(bytes)?;
179 fs::rename(&temp, &target)?;
180 Self::sync_dir(parent)
181 }
182
183 fn create_new(&self, path: &str, bytes: &[u8]) -> io::Result<bool> {
184 let target = self.resolve(path)?;
185 let parent = target
186 .parent()
187 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "no parent"))?;
188 fs::create_dir_all(parent)?;
189 match OpenOptions::new()
190 .write(true)
191 .create_new(true)
192 .open(&target)
193 {
194 Ok(mut file) => {
195 file.write_all(bytes)?;
196 file.sync_all()?;
197 Self::sync_dir(parent)?;
198 Ok(true)
199 }
200 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(false),
201 Err(e) => Err(e),
202 }
203 }
204
205 fn delete(&self, path: &str) -> io::Result<bool> {
206 let target = self.resolve(path)?;
207 match fs::remove_file(&target) {
208 Ok(()) => {
209 if let Some(parent) = target.parent() {
210 Self::sync_dir(parent)?;
211 }
212 Ok(true)
213 }
214 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
215 Err(e) => Err(e),
216 }
217 }
218
219 fn rename(&self, from: &str, to: &str) -> io::Result<()> {
220 let source = self.resolve(from)?;
221 let target = self.resolve(to)?;
222 if let Some(parent) = target.parent() {
223 fs::create_dir_all(parent)?;
224 }
225 fs::rename(&source, &target)?;
226 if let Some(parent) = target.parent() {
227 Self::sync_dir(parent)?;
228 }
229 if let Some(parent) = source.parent() {
230 Self::sync_dir(parent)?;
231 }
232 Ok(())
233 }
234
235 fn list(&self, prefix: &str) -> io::Result<Vec<String>> {
236 let dir = self.resolve(prefix)?;
237 let mut out = Vec::new();
238 match fs::metadata(&dir) {
239 Ok(meta) if meta.is_dir() => Self::walk(&dir, &self.root, &mut out)?,
240 Ok(_) => {}
241 Err(e) if e.kind() == io::ErrorKind::NotFound => {}
242 Err(e) => return Err(e),
243 }
244 if prefix.is_empty() {
245 out.retain(|p| !p.starts_with(".tmp/"));
246 }
247 out.sort();
248 Ok(out)
249 }
250
251 fn exists(&self, path: &str) -> io::Result<bool> {
252 Ok(self.resolve(path)?.is_file())
253 }
254}
255
256#[derive(Debug, Clone, Default)]
259pub struct MemoryStore {
260 files: Arc<Mutex<BTreeMap<String, Vec<u8>>>>,
261}
262
263impl MemoryStore {
264 #[must_use]
266 pub fn new() -> Self {
267 Self::default()
268 }
269
270 #[must_use]
272 pub fn total_bytes(&self) -> usize {
273 self.files.lock().unwrap().values().map(Vec::len).sum()
274 }
275
276 #[must_use]
278 pub fn bytes_under(&self, prefix: &str) -> usize {
279 self.files
280 .lock()
281 .unwrap()
282 .iter()
283 .filter(|(k, _)| under(k, prefix))
284 .map(|(_, v)| v.len())
285 .sum()
286 }
287}
288
289fn under(path: &str, prefix: &str) -> bool {
290 prefix.is_empty()
291 || path
292 .strip_prefix(prefix)
293 .is_some_and(|r| r.starts_with('/'))
294}
295
296impl TileStore for MemoryStore {
297 fn get(&self, path: &str) -> io::Result<Option<Vec<u8>>> {
298 Ok(self.files.lock().unwrap().get(path).cloned())
299 }
300
301 fn put(&self, path: &str, bytes: &[u8]) -> io::Result<()> {
302 self.files
303 .lock()
304 .unwrap()
305 .insert(path.to_string(), bytes.to_vec());
306 Ok(())
307 }
308
309 fn create_new(&self, path: &str, bytes: &[u8]) -> io::Result<bool> {
310 let mut files = self.files.lock().unwrap();
311 if files.contains_key(path) {
312 return Ok(false);
313 }
314 files.insert(path.to_string(), bytes.to_vec());
315 Ok(true)
316 }
317
318 fn delete(&self, path: &str) -> io::Result<bool> {
319 Ok(self.files.lock().unwrap().remove(path).is_some())
320 }
321
322 fn rename(&self, from: &str, to: &str) -> io::Result<()> {
323 let mut files = self.files.lock().unwrap();
324 let bytes = files
325 .remove(from)
326 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, from.to_string()))?;
327 files.insert(to.to_string(), bytes);
328 Ok(())
329 }
330
331 fn list(&self, prefix: &str) -> io::Result<Vec<String>> {
332 Ok(self
333 .files
334 .lock()
335 .unwrap()
336 .keys()
337 .filter(|k| under(k, prefix))
338 .cloned()
339 .collect())
340 }
341}