truffle_core/synced_store/
backend.rs1use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7pub trait StoreBackend: Send + Sync + 'static {
13 fn load(&self, store_id: &str, device_id: &str) -> Option<(Vec<u8>, u64)>;
15
16 fn save(&self, store_id: &str, device_id: &str, data: &[u8], version: u64);
18
19 fn remove(&self, store_id: &str, device_id: &str);
21}
22
23#[derive(Debug, Default)]
29pub struct MemoryBackend;
30
31impl StoreBackend for MemoryBackend {
32 fn load(&self, _store_id: &str, _device_id: &str) -> Option<(Vec<u8>, u64)> {
33 None
34 }
35
36 fn save(&self, _store_id: &str, _device_id: &str, _data: &[u8], _version: u64) {}
37
38 fn remove(&self, _store_id: &str, _device_id: &str) {}
39}
40
41#[derive(Serialize, Deserialize)]
47struct StoredSlice {
48 data: Vec<u8>,
49 version: u64,
50}
51
52pub struct FileBackend {
58 base_dir: PathBuf,
59}
60
61impl FileBackend {
62 pub fn new(base_dir: impl Into<PathBuf>) -> Self {
64 Self {
65 base_dir: base_dir.into(),
66 }
67 }
68
69 fn path(&self, store_id: &str, device_id: &str) -> PathBuf {
73 let safe_store = sanitize(store_id);
74 let safe_device = sanitize(device_id);
75 self.base_dir.join(safe_store).join(format!("{safe_device}.json"))
76 }
77}
78
79fn sanitize(s: &str) -> String {
81 s.replace('/', "_")
82 .replace('\\', "_")
83 .replace("..", "_")
84}
85
86impl StoreBackend for FileBackend {
87 fn load(&self, store_id: &str, device_id: &str) -> Option<(Vec<u8>, u64)> {
88 let path = self.path(store_id, device_id);
89 let contents = std::fs::read_to_string(&path).ok()?;
90 let stored: StoredSlice = serde_json::from_str(&contents).ok()?;
91 Some((stored.data, stored.version))
92 }
93
94 fn save(&self, store_id: &str, device_id: &str, data: &[u8], version: u64) {
95 let path = self.path(store_id, device_id);
96
97 if let Some(parent) = path.parent() {
99 if let Err(e) = std::fs::create_dir_all(parent) {
100 tracing::error!(
101 path = %parent.display(),
102 "file_backend: failed to create directory: {e}"
103 );
104 return;
105 }
106 }
107
108 let stored = StoredSlice {
109 data: data.to_vec(),
110 version,
111 };
112
113 let json = match serde_json::to_string(&stored) {
114 Ok(j) => j,
115 Err(e) => {
116 tracing::error!("file_backend: failed to serialize: {e}");
117 return;
118 }
119 };
120
121 let tmp_path = path.with_extension("json.tmp");
123 if let Err(e) = std::fs::write(&tmp_path, json.as_bytes()) {
124 tracing::error!(
125 path = %tmp_path.display(),
126 "file_backend: failed to write tmp file: {e}"
127 );
128 return;
129 }
130 if let Err(e) = std::fs::rename(&tmp_path, &path) {
131 tracing::error!(
132 from = %tmp_path.display(),
133 to = %path.display(),
134 "file_backend: failed to rename tmp file: {e}"
135 );
136 }
137 }
138
139 fn remove(&self, store_id: &str, device_id: &str) {
140 let path = self.path(store_id, device_id);
141 let _ = std::fs::remove_file(&path);
142 }
143}