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}