Skip to main content

rmskin_builder/
discover.rs

1use std::{
2    fs,
3    path::{Path, PathBuf},
4};
5
6#[cfg(feature = "py-binding")]
7use pyo3::prelude::*;
8
9use crate::file_utils::{RMSKIN_BMP_NAME, RMSKIN_INI_NAME};
10
11/// A data structure to represent the presence of a
12/// Rainmeter project's distributable components.
13#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
14#[cfg_attr(
15    feature = "py-binding",
16    pyclass(get_all, set_all, module = "rmskin_builder", eq, from_py_object)
17)]
18pub struct HasComponents {
19    pub rm_skin_ini: bool,
20    pub skins: i32,
21    pub layouts: i32,
22    pub plugins: bool,
23    pub vault: i32,
24    pub rm_skin_bmp: bool,
25}
26
27impl HasComponents {
28    pub const VAULT: &str = "@Vault";
29    pub const PLUGINS: &str = "Plugins";
30    pub const LAYOUTS: &str = "Layouts";
31    pub const SKINS: &str = "Skins";
32
33    pub fn is_valid(&self) -> bool {
34        self.rm_skin_ini && self.skins > 0
35    }
36}
37
38#[cfg(feature = "py-binding")]
39#[cfg_attr(feature = "py-binding", pymethods)]
40impl HasComponents {
41    #[new]
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    pub fn __repr__(&self) -> String {
47        format!("{self:?}")
48    }
49
50    #[pyo3(name = "is_valid")]
51    pub fn is_valid_py(&self) -> bool {
52        self.is_valid()
53    }
54}
55
56fn count_dir_children(path: PathBuf, dirs: bool, files: bool) -> Result<i32, std::io::Error> {
57    let mut count = 0;
58    for entry in (fs::read_dir(path)?).flatten() {
59        let entry_path = entry.path();
60        if (entry_path.is_dir() && dirs) || (entry_path.is_file() && files) {
61            count += 1;
62        }
63    }
64    Ok(count)
65}
66
67/// The method that does preliminary discovery of rmskin package components.
68#[cfg_attr(feature = "py-binding", pyfunction(name = "discover_components"))]
69#[cfg(feature = "py-binding")]
70pub fn discover_components_py(path: PathBuf) -> PyResult<HasComponents> {
71    use pyo3::exceptions::PyIOError;
72
73    discover_components(&path).map_err(|e| PyIOError::new_err(e.to_string()))
74}
75
76/// The method that does preliminary discovery of rmskin package components.
77pub fn discover_components<P: AsRef<Path>>(path: P) -> Result<HasComponents, std::io::Error> {
78    let mut components = HasComponents::default();
79    for entry in fs::read_dir(path)? {
80        let entry_path = entry?.path();
81        if let Some(dir_name) = entry_path.file_name() {
82            let name = dir_name.to_string_lossy().to_string();
83            match name.as_str() {
84                HasComponents::SKINS => {
85                    let count = count_dir_children(entry_path, true, false)?;
86                    log::info!("Found {count} possible skin(s)");
87                    components.skins = count;
88                }
89                HasComponents::VAULT => {
90                    let count = count_dir_children(entry_path, true, true)?;
91                    log::info!("Found {count} possible @Vault item(s)");
92                    components.vault = count;
93                }
94                HasComponents::PLUGINS => {
95                    let count = count_dir_children(entry_path, true, false)?;
96                    log::info!("Found {count} Plugins folder");
97                    components.plugins = true;
98                }
99                HasComponents::LAYOUTS => {
100                    let count = count_dir_children(entry_path, true, true)?;
101                    log::info!("Found {count} possible layout(s)");
102                    components.layouts = count;
103                }
104                RMSKIN_INI_NAME => {
105                    log::info!("Found RMSKIN.ini file");
106                    components.rm_skin_ini = true;
107                }
108                RMSKIN_BMP_NAME => {
109                    log::info!("Found header image file");
110                    components.rm_skin_bmp = true;
111                }
112                _ => log::debug!("Skipping {name}"),
113            }
114        }
115    }
116    Ok(components)
117}
118
119#[cfg(test)]
120mod test {
121    use super::{HasComponents, discover_components};
122
123    #[test]
124    fn basic() {
125        let components = discover_components("tests/demo_project").unwrap();
126        assert!(components.is_valid());
127        let expected = HasComponents {
128            rm_skin_ini: true,
129            skins: 1,
130            layouts: 1,
131            plugins: true,
132            vault: 1,
133            rm_skin_bmp: true,
134        };
135        assert_eq!(components, expected);
136    }
137}