1use alloc::borrow::ToOwned;
13use alloc::collections::BTreeSet;
14use alloc::format;
15use alloc::string::String;
16use alloc::vec::Vec;
17
18use shuyi_ui::storage::{Storage, StorageError, conformance_suite, is_valid_key};
19
20use crate::sdk::{Sdk, backend};
21
22#[derive(Debug)]
24pub struct SdkStorage<S> {
25 sdk: S,
26 folder: String,
29 swept: bool,
31}
32
33impl<S: Sdk> SdkStorage<S> {
34 pub fn new(sdk: S) -> Self {
36 Self {
37 sdk,
38 folder: String::new(),
39 swept: false,
40 }
41 }
42
43 pub fn in_folder(sdk: S, folder: &str) -> Self {
46 Self {
47 sdk,
48 folder: format!("{folder}/"),
49 swept: false,
50 }
51 }
52
53 fn path(&self, key: &str) -> String {
54 format!("{}{key}.ron", self.folder)
55 }
56
57 fn tmp_path(&self, key: &str) -> String {
58 format!("{}{key}.ron.tmp", self.folder)
59 }
60
61 fn bak_path(&self, key: &str) -> String {
62 format!("{}{key}.ron.bak", self.folder)
63 }
64
65 fn names(&self) -> Result<Vec<String>, StorageError> {
67 let folder = self.folder.trim_end_matches('/');
68 self.sdk.data_names(folder).map_err(|e| backend(folder, &e))
69 }
70
71 fn good_backup(&self, key: &str) -> Option<String> {
73 let bak = self.bak_path(key);
74 if !self.sdk.is_file(&bak) {
75 return None;
76 }
77 let bytes = self.sdk.read_data(&bak).ok()?;
78 if is_good(&bytes) {
79 String::from_utf8(bytes).ok()
80 } else {
81 None
82 }
83 }
84
85 fn sweep(&self) -> Result<(), StorageError> {
87 for name in self.names()?.iter().filter(|n| n.ends_with(".ron.tmp")) {
88 self.remove_if_there(&format!("{}{name}", self.folder))?;
89 }
90 Ok(())
91 }
92
93 fn remove_if_there(&self, path: &str) -> Result<(), StorageError> {
94 if self.sdk.is_file(path) {
95 self.sdk.remove_data(path).map_err(|e| backend(path, &e))?;
96 }
97 Ok(())
98 }
99
100 fn replace(&self, tmp: &str, dest: &str) -> Result<(), StorageError> {
103 if self.sdk.rename_data(tmp, dest).is_ok() {
104 return Ok(());
105 }
106 self.remove_if_there(dest)?;
107 self.sdk
108 .rename_data(tmp, dest)
109 .map_err(|e| backend(dest, &e))
110 }
111}
112
113fn is_good(bytes: &[u8]) -> bool {
116 !bytes.is_empty() && bytes.iter().any(|&b| b != 0) && core::str::from_utf8(bytes).is_ok()
117}
118
119fn check_key(key: &str) -> Result<(), StorageError> {
120 if is_valid_key(key) {
121 Ok(())
122 } else {
123 Err(StorageError::InvalidKey(key.to_owned()))
124 }
125}
126
127impl<S: Sdk> Storage for SdkStorage<S> {
128 fn read(&self, key: &str) -> Result<Option<String>, StorageError> {
129 check_key(key)?;
130 let path = self.path(key);
131 let main = if self.sdk.is_file(&path) {
132 Some(self.sdk.read_data(&path).map_err(|e| backend(&path, &e))?)
133 } else {
134 None
135 };
136 if !main.as_deref().is_some_and(is_good)
137 && let Some(backup) = self.good_backup(key)
138 {
139 return Ok(Some(backup));
140 }
141 main.map(String::from_utf8)
142 .transpose()
143 .map_err(|e| backend(&path, &format!("{e}")))
144 }
145
146 fn write(&mut self, key: &str, value: &str) -> Result<(), StorageError> {
147 check_key(key)?;
148 if !self.swept {
149 let folder = self.folder.trim_end_matches('/');
150 if !folder.is_empty() && self.sdk.data_names(folder).is_err() {
151 self.sdk
152 .make_data_folder(folder)
153 .map_err(|e| backend(folder, &e))?;
154 }
155 self.sweep()?;
156 self.swept = true;
157 }
158 let tmp = self.tmp_path(key);
159 self.sdk
160 .write_data(&tmp, value.as_bytes())
161 .map_err(|e| backend(&tmp, &e))?;
162 let dest = self.path(key);
163 let bak = self.bak_path(key);
164 if is_good(value.as_bytes()) {
165 let old = if self.sdk.is_file(&dest) {
166 Some(self.sdk.read_data(&dest).map_err(|e| backend(&dest, &e))?)
167 } else {
168 None
169 };
170 if let Some(old) = old.filter(|old| is_good(old)) {
171 self.sdk
172 .write_data(&bak, &old)
173 .map_err(|e| backend(&bak, &e))?;
174 }
175 } else {
176 self.remove_if_there(&bak)?;
177 }
178 self.replace(&tmp, &dest)
179 }
180
181 fn delete(&mut self, key: &str) -> Result<(), StorageError> {
182 check_key(key)?;
183 self.remove_if_there(&self.bak_path(key))?;
184 self.remove_if_there(&self.path(key))
185 }
186
187 fn list(&self) -> Result<Vec<String>, StorageError> {
188 let names = if self.folder.is_empty() {
189 self.names()?
190 } else {
191 self.names().unwrap_or_default()
193 };
194 let mut keys = BTreeSet::new();
195 for name in &names {
196 if let Some(key) = name.strip_suffix(".ron") {
197 if is_valid_key(key) {
198 keys.insert(key.to_owned());
199 }
200 } else if let Some(key) = name.strip_suffix(".ron.bak")
201 && is_valid_key(key)
202 && !names.contains(&format!("{key}.ron"))
203 && self.good_backup(key).is_some()
204 {
205 keys.insert(key.to_owned());
206 }
207 }
208 Ok(keys.into_iter().collect())
209 }
210}
211
212pub const CHECK_FOLDER: &str = "storage-check";
215
216pub fn check<S: Sdk>(sdk: S) {
220 let mut storage = SdkStorage::in_folder(sdk, CHECK_FOLDER);
221 let clear = |storage: &mut SdkStorage<S>| {
222 let keys = storage.list().unwrap_or_else(|e| panic!("{e}"));
223 for key in keys {
224 storage.delete(&key).unwrap_or_else(|e| panic!("{e}"));
225 }
226 };
227 clear(&mut storage);
228 conformance_suite(&mut storage);
229 clear(&mut storage);
230}
231
232#[cfg(test)]
233mod tests {
234 use alloc::string::ToString;
235
236 use shuyi_ui::storage::{PowerCutRig, power_cut_suite};
237
238 use super::*;
239 use crate::fake::{FakeSdk, Rename};
240
241 #[derive(Debug)]
243 struct Rig {
244 sdk: FakeSdk,
245 storage: SdkStorage<FakeSdk>,
246 }
247
248 impl Rig {
249 fn new(rename: Rename) -> Self {
250 let sdk = FakeSdk::default();
251 sdk.set_rename(rename);
252 Self {
253 storage: SdkStorage::new(sdk.clone()),
254 sdk,
255 }
256 }
257 }
258
259 impl PowerCutRig for Rig {
260 fn storage(&mut self) -> &mut dyn Storage {
261 &mut self.storage
262 }
263
264 fn cut_after(&mut self, ops: u32) {
265 self.sdk.cut_power_after(ops);
266 }
267
268 fn restart(&mut self) {
269 self.sdk.restore_power();
270 self.storage = SdkStorage::new(self.sdk.clone());
271 }
272 }
273
274 #[test]
275 fn it_passes_the_conformance_suite() {
276 conformance_suite(&mut SdkStorage::new(FakeSdk::default()));
277 }
278
279 #[test]
280 fn it_passes_where_a_file_cannot_be_moved_onto_another() {
281 let sdk = FakeSdk::default();
282 sdk.set_rename(Rename::Refuses);
283 conformance_suite(&mut SdkStorage::new(sdk));
284 }
285
286 #[test]
287 fn it_passes_in_a_folder_and_the_check_leaves_nothing() {
288 conformance_suite(&mut SdkStorage::in_folder(FakeSdk::default(), "saves"));
289 let sdk = FakeSdk::default();
290 sdk.put_file("settings.ron", b"(a)");
291 check(sdk.clone());
292 check(sdk.clone());
293 assert_eq!(
294 sdk.data_names("").unwrap_or_default(),
295 ["settings.ron".to_string(), "storage-check/".to_string()]
296 );
297 assert_eq!(
298 sdk.data_names(CHECK_FOLDER).unwrap_or_default(),
299 [] as [String; 0]
300 );
301 }
302
303 #[test]
304 fn it_passes_the_power_cut_suite() {
305 power_cut_suite(&mut Rig::new(Rename::Replaces));
306 }
307
308 #[test]
309 fn it_passes_the_power_cut_suite_where_a_file_cannot_be_moved_onto_another() {
310 power_cut_suite(&mut Rig::new(Rename::Refuses));
311 }
312
313 #[test]
314 fn a_write_goes_tmp_then_bak_then_rename_each_flushed() {
315 let sdk = FakeSdk::default();
316 let mut storage = SdkStorage::new(sdk.clone());
317 storage
318 .write("slot", "(a)")
319 .unwrap_or_else(|e| panic!("{e}"));
320 sdk.take_file_calls();
321 storage
322 .write("slot", "(b)")
323 .unwrap_or_else(|e| panic!("{e}"));
324 assert_eq!(
325 sdk.take_file_calls(),
326 [
327 "write slot.ron.tmp",
328 "write slot.ron.bak",
329 "rename slot.ron.tmp slot.ron",
330 ]
331 );
332 assert_eq!(sdk.file("slot.ron.bak").as_deref(), Some(&b"(a)"[..]));
333 assert_eq!(
334 storage.read("slot").unwrap_or_default().as_deref(),
335 Some("(b)")
336 );
337 }
338
339 #[test]
340 fn damage_reads_the_backup() {
341 let sdk = FakeSdk::default();
342 let mut storage = SdkStorage::new(sdk.clone());
343 storage
344 .write("slot", "(a)")
345 .unwrap_or_else(|e| panic!("{e}"));
346 storage
347 .write("slot", "(b)")
348 .unwrap_or_else(|e| panic!("{e}"));
349 sdk.put_file("slot.ron", &[0, 0, 0]);
350 assert_eq!(
351 storage.read("slot").unwrap_or_default().as_deref(),
352 Some("(a)")
353 );
354 sdk.put_file("slot.ron", &[0xff]);
355 sdk.put_file("slot.ron.bak", &[0xff]);
356 assert!(matches!(
357 storage.read("slot"),
358 Err(StorageError::Backend(_))
359 ));
360 }
361
362 #[test]
363 fn only_keys_are_listed() {
364 let sdk = FakeSdk::default();
365 let mut storage = SdkStorage::new(sdk.clone());
366 storage
367 .write("settings", "()")
368 .unwrap_or_else(|e| panic!("{e}"));
369 sdk.put_file("load-report.txt", b"");
370 sdk.put_file("Bad Name.ron", b"");
371 sdk.put_file("slot.ron.tmp", b"");
372 let keys = storage.list().unwrap_or_else(|e| panic!("{e}"));
373 assert_eq!(keys, ["settings".to_string()]);
374 }
375
376 #[test]
377 fn the_first_write_removes_what_a_crash_left() {
378 let sdk = FakeSdk::default();
379 sdk.put_file("slot.ron.tmp", b"half");
380 let mut storage = SdkStorage::new(sdk.clone());
381 storage
382 .write("other", "(a)")
383 .unwrap_or_else(|e| panic!("{e}"));
384 assert_eq!(sdk.file("slot.ron.tmp"), None);
385 }
386
387 #[test]
388 fn a_data_folder_that_cant_be_listed_is_an_error_and_a_folder_not_made_holds_nothing() {
389 let sdk = FakeSdk::default();
390 assert_eq!(
391 SdkStorage::in_folder(sdk.clone(), "saves").list().ok(),
392 Some(Vec::new())
393 );
394 sdk.cut_power_after(0);
395 assert!(matches!(
396 SdkStorage::new(sdk).list(),
397 Err(StorageError::Backend(_))
398 ));
399 }
400
401 #[test]
402 fn a_bad_key_is_an_error() {
403 let mut storage = SdkStorage::new(FakeSdk::default());
404 assert!(matches!(
405 storage.read("a/b"),
406 Err(StorageError::InvalidKey(_))
407 ));
408 assert!(matches!(
409 storage.write("a/b", "x"),
410 Err(StorageError::InvalidKey(_))
411 ));
412 assert!(matches!(
413 storage.delete("a/b"),
414 Err(StorageError::InvalidKey(_))
415 ));
416 }
417}