1use parking_lot::Mutex;
2use std::collections::HashMap;
3use std::fs;
4use std::path::{Path, PathBuf};
5
6pub trait Storage: Send + Sync {
8 fn get_raw(&self, key: &str) -> Option<String>;
10 fn save_raw(&self, key: &str, value: &str) -> std::io::Result<()>;
12}
13
14pub struct MemoryStore {
16 storage: Mutex<HashMap<String, String>>,
17}
18
19impl MemoryStore {
20 pub fn new() -> Self {
21 Self {
22 storage: Mutex::new(HashMap::new()),
23 }
24 }
25}
26
27impl Default for MemoryStore {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl Storage for MemoryStore {
34 fn get_raw(&self, key: &str) -> Option<String> {
35 self.storage.lock().get(key).cloned()
36 }
37
38 fn save_raw(&self, key: &str, value: &str) -> std::io::Result<()> {
39 self.storage
40 .lock()
41 .insert(key.to_string(), value.to_string());
42 Ok(())
43 }
44}
45
46pub struct FileStore {
49 dir: PathBuf,
50 write_lock: Mutex<()>,
51}
52
53impl FileStore {
54 pub fn new<P: AsRef<Path>>(dir: P) -> std::io::Result<Self> {
56 let dir = dir.as_ref().to_path_buf();
57 fs::create_dir_all(&dir)?;
58 Ok(Self {
59 dir,
60 write_lock: Mutex::new(()),
61 })
62 }
63
64 fn key_path(&self, key: &str) -> PathBuf {
65 let safe: String = key
66 .chars()
67 .map(|c| {
68 if c.is_ascii_alphanumeric() || c == '_' || c == '-' {
69 c
70 } else {
71 '_'
72 }
73 })
74 .collect();
75 self.dir.join(safe)
76 }
77}
78
79impl Storage for FileStore {
80 fn get_raw(&self, key: &str) -> Option<String> {
81 let path = self.key_path(key);
82 fs::read_to_string(path).ok()
83 }
84
85 fn save_raw(&self, key: &str, value: &str) -> std::io::Result<()> {
86 let _g = self.write_lock.lock();
87 let path = self.key_path(key);
88 fs::write(&path, value)
89 }
90}