mister_fpga/
core_info.rs

1use crate::config;
2use std::ffi::OsStr;
3use std::path::{Path, PathBuf};
4
5fn strip_version(name: &str) -> &str {
6    name.rsplit_once('_').map(|(name, _)| name).unwrap_or(name)
7}
8
9fn core_name_of_(path: &Path) -> Option<&str> {
10    Some(strip_version(exact_core_name_of_(path)?))
11}
12
13fn exact_core_name_of_(path: &Path) -> Option<&str> {
14    if path.extension().and_then(OsStr::to_str) != Some("rbf") {
15        return None;
16    }
17
18    path.file_stem()?.to_str()
19}
20
21fn find_core_(path: &Path, name: &str) -> Result<Option<PathBuf>, std::io::Error> {
22    let stripped_name = strip_version(name);
23
24    for entry in path.read_dir()? {
25        let entry = entry?;
26        let path = entry.path();
27
28        if path.is_dir() {
29            let file_name = path.file_name().and_then(OsStr::to_str);
30            if file_name.is_none() || !file_name.unwrap().starts_with('_') {
31                continue;
32            }
33            if let Some(core) = find_core_(&path, name)? {
34                return Ok(Some(core));
35            }
36        } else {
37            let current_path = path.file_stem().and_then(OsStr::to_str);
38            if let Some(current_path) = current_path {
39                if strip_version(current_path) == stripped_name {
40                    return Ok(Some(entry.path()));
41                }
42            }
43        }
44    }
45    Ok(None)
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct CoreInfo {
50    name: String,
51    path: PathBuf,
52}
53
54impl CoreInfo {
55    pub fn new(name: impl ToString, path: impl Into<PathBuf>) -> Self {
56        Self {
57            name: name.to_string(),
58            path: path.into(),
59        }
60    }
61
62    pub fn from_path(path: impl AsRef<Path>) -> Option<Self> {
63        let path = path.as_ref();
64        let name = core_name_of_(path)?;
65        Some(Self::new(name, path))
66    }
67
68    pub fn from_name(
69        name: impl AsRef<str>,
70        core_root_dir: impl AsRef<Path>,
71    ) -> Result<Option<Self>, std::io::Error> {
72        let name = name.as_ref();
73        let path = find_core_(core_root_dir.as_ref(), name)?;
74
75        if let Some(path) = path {
76            // Override name with core_name_of, to make sure we remove the version number if any.
77            let name = core_name_of_(&path);
78            Ok(name.map(|name| (Self::new(name, &path))))
79        } else {
80            Ok(None)
81        }
82    }
83
84    pub fn from_exact_name(
85        name: impl AsRef<str>,
86        core_root_dir: impl AsRef<Path>,
87    ) -> Result<Option<Self>, std::io::Error> {
88        let name = name.as_ref();
89        let path = find_core_(core_root_dir.as_ref(), name)?;
90
91        if let Some(path) = path {
92            // Check that the exact name is the same.
93            if exact_core_name_of_(&path) != Some(name) {
94                return Ok(None);
95            }
96
97            // Override name with core_name_of, to make sure we remove the version number if any.
98            let name = core_name_of_(&path);
99            Ok(name.map(|name| (Self::new(name, &path))))
100        } else {
101            Ok(None)
102        }
103    }
104
105    pub fn name(&self) -> &str {
106        &self.name
107    }
108
109    pub fn path(&self) -> &Path {
110        &self.path
111    }
112
113    pub fn exact_name(&self) -> &str {
114        exact_core_name_of_(&self.path).unwrap()
115    }
116}
117
118impl From<config::BootCoreConfig> for Option<CoreInfo> {
119    fn from(value: config::BootCoreConfig) -> Self {
120        match value {
121            config::BootCoreConfig::None => None,
122            config::BootCoreConfig::LastCore => {
123                let last_core_name = config::Config::last_core_data()?;
124                Some(CoreInfo::from_name(last_core_name, config::Config::cores_root()).ok()??)
125            }
126            config::BootCoreConfig::ExactLastCore => {
127                let last_core_name = config::Config::last_core_data()?;
128                Some(
129                    CoreInfo::from_exact_name(last_core_name, config::Config::cores_root())
130                        .ok()??,
131                )
132            }
133            config::BootCoreConfig::CoreName(name) => {
134                Some(CoreInfo::from_name(name, config::Config::cores_root()).ok()??)
135            }
136        }
137    }
138}
139
140#[test]
141fn from_path_works() {
142    let core = CoreInfo::from_path("config/cores/Somecore_12345678.rbf").unwrap();
143    assert_eq!(core.name, "Somecore");
144    assert_eq!(core.path, Path::new("config/cores/Somecore_12345678.rbf"));
145}
146
147#[test]
148fn from_path_works_none() {
149    let core = CoreInfo::from_path("config/cores/Somecore_12345678");
150    assert_eq!(core, None);
151}
152
153#[test]
154fn from_path_works_exact() {
155    let core = CoreInfo::from_path("config/cores/Somecore.rbf").unwrap();
156    assert_eq!(core.name, "Somecore");
157    assert_eq!(core.path, Path::new("config/cores/Somecore.rbf"));
158}
159
160#[test]
161fn from_name_works() {
162    let root_dir = tempdir::TempDir::new("mister").unwrap();
163    let root = root_dir.path();
164
165    // Create a structure like this:
166    //   mister/
167    //   └── config/
168    //       └── Core_12345678.rbf
169    //       └── Wrong_12345678.rbf
170    //   └── _Cores/
171    //       └── Core_12345678.rbf
172    //       └── hello.rbf
173    //   └── _Other/
174    //       └── Other_12345678.rbf
175    //       └── _Again/
176    //           └── Bar_12345678.rbf
177
178    std::fs::create_dir_all(root.join("config")).unwrap();
179    std::fs::create_dir_all(root.join("_Cores")).unwrap();
180    std::fs::create_dir_all(root.join("_Other")).unwrap();
181    std::fs::create_dir_all(root.join("_Other/_Again")).unwrap();
182    std::fs::write(root.join("config/Core_12345678.rbf"), "").unwrap();
183    std::fs::write(root.join("_Cores/Core_12345678.rbf"), "").unwrap();
184    std::fs::write(root.join("_Cores/hello.rbf"), "").unwrap();
185    std::fs::write(root.join("_Other/Other_12345678.rbf"), "").unwrap();
186    std::fs::write(root.join("_Other/_Again/Bar_12345678.rbf"), "").unwrap();
187
188    // Testing base case.
189    // Core_12345678 should be in $root/_Cores/ (not $root/config/).
190    let core = CoreInfo::from_name("Core", root).unwrap().unwrap();
191    assert_eq!(core.name, "Core");
192    assert_eq!(core.path, root.join("_Cores/Core_12345678.rbf"));
193
194    // Testing no version number.
195    // hello should be in $root/_Cores/ (not $root/config/).
196    let core = CoreInfo::from_name("hello", root).unwrap().unwrap();
197    assert_eq!(core.name, "hello");
198    assert_eq!(core.path, root.join("_Cores/hello.rbf"));
199
200    // Testing iterative over directories.
201    // Other_12345678 should be in $root/_Other/ (not $root/config/).
202    let core = CoreInfo::from_name("Other", root).unwrap().unwrap();
203    assert_eq!(core.name, "Other");
204    assert_eq!(core.path, root.join("_Other/Other_12345678.rbf"));
205
206    // Testing recursive over directories.
207    // Other_12345678 should be in $root/_Other/ (not $root/config/).
208    let core = CoreInfo::from_name("Bar", root).unwrap().unwrap();
209    assert_eq!(core.name, "Bar");
210    assert_eq!(core.path, root.join("_Other/_Again/Bar_12345678.rbf"));
211
212    // Testing skipping directories not starting with `_`.
213    let core = CoreInfo::from_name("Wrong", root).unwrap();
214    assert_eq!(core, None);
215}
216
217#[test]
218fn from_bootcore_config() {
219    let root_dir = tempdir::TempDir::new("mister").unwrap();
220    let root = root_dir.path();
221    config::testing::set_config_root(root);
222
223    // Create a structure like this:
224    //   mister/
225    //   └── config/
226    //       └── lastcore.dat (contains "Core")
227    //       └── Core_12345678.rbf
228    //       └── Wrong_12345678.rbf
229    //   └── _Cores/
230    //       └── Core_12345678.rbf
231    //       └── hello.rbf
232    //   └── _Other/
233    //       └── Other_12345678.rbf
234    //       └── _Again/
235    //           └── Bar_12345678.rbf
236
237    std::fs::create_dir_all(root.join("config")).unwrap();
238    std::fs::create_dir_all(root.join("_Cores")).unwrap();
239    std::fs::create_dir_all(root.join("_Other")).unwrap();
240    std::fs::create_dir_all(root.join("_Other/_Again")).unwrap();
241    std::fs::write(root.join("config/Core_12345678.rbf"), "").unwrap();
242    std::fs::write(root.join("config/lastcore.dat"), "Core").unwrap();
243    std::fs::write(root.join("_Cores/Core_12345678.rbf"), "").unwrap();
244    std::fs::write(root.join("_Cores/hello.rbf"), "").unwrap();
245    std::fs::write(root.join("_Other/Other_12345678.rbf"), "").unwrap();
246    std::fs::write(root.join("_Other/_Again/Bar_12345678.rbf"), "").unwrap();
247
248    let x: Option<CoreInfo> = Option::<CoreInfo>::from(config::BootCoreConfig::LastCore);
249    let core = x.unwrap();
250    assert_eq!(core.name, "Core");
251    assert_eq!(core.path, root.join("_Cores/Core_12345678.rbf"));
252}