trueno/tuner/brick_tuner/
persistence.rs1use super::super::error::TunerError;
7use super::super::helpers::crc32_update;
8use super::BrickTuner;
9
10impl BrickTuner {
11 const APR_MAGIC: [u8; 4] = [b'A', b'P', b'R', b'1'];
13
14 pub fn to_json(&self) -> Result<String, TunerError> {
16 serde_json::to_string_pretty(self).map_err(|e| TunerError::Serialization(e.to_string()))
17 }
18
19 pub fn from_json(json: &str) -> Result<Self, TunerError> {
21 serde_json::from_str(json).map_err(|e| TunerError::Serialization(e.to_string()))
22 }
23
24 #[cfg(feature = "hardware-detect")]
32 pub fn cache_path() -> std::path::PathBuf {
33 let cache_dir =
34 dirs::cache_dir().unwrap_or_else(|| std::path::PathBuf::from(".")).join("trueno");
35
36 let _ = std::fs::create_dir_all(&cache_dir);
38
39 cache_dir.join(format!("tuner_model_v{}.apr", Self::VERSION))
40 }
41
42 #[cfg(feature = "hardware-detect")]
50 pub fn load_or_default() -> Self {
51 let path = Self::cache_path();
52
53 if path.exists() {
54 match Self::load_apr(&path) {
55 Ok(tuner) => {
56 if tuner.version == Self::VERSION {
58 return tuner;
59 }
60 }
62 Err(_) => {
63 }
65 }
66 }
67
68 Self::new()
69 }
70
71 pub fn save_apr<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), TunerError> {
79 use std::io::Write;
80
81 let json = self.to_json()?;
82 let json_bytes = json.as_bytes();
83
84 let mut file = std::fs::File::create(path).map_err(|e| TunerError::Io(e.to_string()))?;
85
86 file.write_all(&Self::APR_MAGIC).map_err(|e| TunerError::Io(e.to_string()))?;
88
89 let len = json_bytes.len() as u32;
91 file.write_all(&len.to_le_bytes()).map_err(|e| TunerError::Io(e.to_string()))?;
92
93 file.write_all(json_bytes).map_err(|e| TunerError::Io(e.to_string()))?;
95
96 let mut crc = 0u32;
98 crc = crc32_update(crc, &Self::APR_MAGIC);
99 crc = crc32_update(crc, &len.to_le_bytes());
100 crc = crc32_update(crc, json_bytes);
101 file.write_all(&crc.to_le_bytes()).map_err(|e| TunerError::Io(e.to_string()))?;
102
103 Ok(())
104 }
105
106 pub fn load_apr<P: AsRef<std::path::Path>>(path: P) -> Result<Self, TunerError> {
108 use std::io::Read;
109
110 let mut file = std::fs::File::open(path).map_err(|e| TunerError::Io(e.to_string()))?;
111
112 let mut magic = [0u8; 4];
114 file.read_exact(&mut magic).map_err(|e| TunerError::Io(e.to_string()))?;
115
116 if magic != Self::APR_MAGIC {
117 return Err(TunerError::InvalidFormat("Invalid APR magic bytes".to_string()));
118 }
119
120 let mut len_bytes = [0u8; 4];
122 file.read_exact(&mut len_bytes).map_err(|e| TunerError::Io(e.to_string()))?;
123 let len = u32::from_le_bytes(len_bytes) as usize;
124
125 let mut json_bytes = vec![0u8; len];
127 file.read_exact(&mut json_bytes).map_err(|e| TunerError::Io(e.to_string()))?;
128
129 let mut crc_bytes = [0u8; 4];
131 file.read_exact(&mut crc_bytes).map_err(|e| TunerError::Io(e.to_string()))?;
132 let stored_crc = u32::from_le_bytes(crc_bytes);
133
134 let mut computed_crc = 0u32;
135 computed_crc = crc32_update(computed_crc, &Self::APR_MAGIC);
136 computed_crc = crc32_update(computed_crc, &len_bytes);
137 computed_crc = crc32_update(computed_crc, &json_bytes);
138
139 if stored_crc != computed_crc {
140 return Err(TunerError::InvalidFormat("CRC32 checksum mismatch".to_string()));
141 }
142
143 let json =
145 String::from_utf8(json_bytes).map_err(|e| TunerError::Serialization(e.to_string()))?;
146
147 Self::from_json(&json)
148 }
149
150 #[cfg(feature = "hardware-detect")]
152 pub fn save_to_cache(&self) -> Result<(), TunerError> {
153 self.save_apr(Self::cache_path())
154 }
155}