veloren_common/util/ron_recover.rs
1use serde::Deserialize;
2use std::{fs, io, path::Path};
3use tracing::warn;
4
5/// Load settings from ron in a recoverable manner. Requires
6/// `#[serde(default)]`.
7///
8/// Lines with parse errors are deleted and parsing is attempted again; this is
9/// repeated until either parsing succeeds or the string is empty.
10/// If there was a parse error, the original file gets renamed to have the
11/// extension `invalid.ron`. Otherwise the disk is not written to.
12pub fn ron_from_path_recoverable<T: Default + for<'a> Deserialize<'a>>(path: &Path) -> T {
13 if let Ok(file) = fs::File::open(path) {
14 let deserialized = match io::read_to_string(file) {
15 Ok(mut serialized) => match ron::from_str::<T>(&serialized) {
16 Ok(s) => return s,
17 Err(e) => {
18 warn!(
19 ?e,
20 ?path,
21 "Failed to parse configuration file! Attempting to recover valid data."
22 );
23 let mut span = e.span;
24 loop {
25 let start: usize = serialized
26 .split_inclusive('\n')
27 .take(span.start.line - 1)
28 .map(|s| s.len())
29 .sum();
30 let end: usize = serialized
31 .split_inclusive('\n')
32 .take(span.end.line)
33 .map(|s| s.len())
34 .sum();
35 drop(serialized.drain(start..end));
36
37 if serialized.is_empty() {
38 warn!(
39 ?path,
40 "Failed to recover anything from configuration file! Fallback to \
41 default."
42 );
43 break T::default();
44 }
45
46 match ron::from_str::<T>(&serialized) {
47 Ok(s) => break s,
48 Err(e) => span = e.span,
49 }
50 }
51 },
52 },
53 Err(e) => {
54 warn!(
55 ?e,
56 ?path,
57 "Failed to read configuration file or convert it to string! Fallback to \
58 default."
59 );
60 T::default()
61 },
62 };
63
64 // Rename the corrupted or outdated configuration file
65 let new_path = path.with_extension("invalid.ron");
66 if let Err(e) = fs::rename(path, &new_path) {
67 warn!(?e, ?path, ?new_path, "Failed to rename configuration file.");
68 }
69
70 deserialized
71 } else {
72 // Presumably the file does not exist
73 T::default()
74 }
75}