Skip to main content

cadmus_core/ota/
cleanup.rs

1use std::io;
2use std::path::Path;
3
4include!(concat!(env!("OUT_DIR"), "/bundled_assets.rs"));
5
6/// Deletes Cadmus-owned bundled files from an install directory before OTA reboot.
7///
8/// Files listed in the generated `BUNDLED_ASSET_FILES` manifest are removed
9/// individually so user-added files in shared asset directories remain intact.
10/// The `libs/` directory is cleaned separately because all shipped shared
11/// libraries are Cadmus-owned.
12pub fn clean_bundled_files(install_dir: &Path) -> io::Result<()> {
13    for asset in BUNDLED_ASSET_FILES {
14        remove_file_if_exists(&install_dir.join(asset))?;
15        remove_empty_parent_dirs(&install_dir.join(asset), install_dir)?;
16    }
17
18    clean_libs_dir(&install_dir.join("libs"))?;
19    remove_empty_parent_dirs(&install_dir.join("libs"), install_dir)?;
20
21    Ok(())
22}
23
24fn clean_libs_dir(libs_dir: &Path) -> io::Result<()> {
25    if let Err(e) = std::fs::remove_dir_all(libs_dir) {
26        if e.kind() != io::ErrorKind::NotFound {
27            return Err(e);
28        }
29    }
30
31    Ok(())
32}
33
34fn remove_file_if_exists(path: &Path) -> io::Result<()> {
35    match std::fs::remove_file(path) {
36        Ok(()) => Ok(()),
37        Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
38        Err(e) => Err(e),
39    }
40}
41
42fn remove_empty_parent_dirs(path: &Path, install_dir: &Path) -> io::Result<()> {
43    let mut current = path.parent();
44
45    while let Some(dir) = current {
46        if dir == install_dir {
47            return Ok(());
48        }
49
50        if !remove_empty_dir_if_exists(dir)? {
51            return Ok(());
52        }
53
54        current = dir.parent();
55    }
56
57    Ok(())
58}
59
60fn remove_empty_dir_if_exists(path: &Path) -> io::Result<bool> {
61    match std::fs::remove_dir(path) {
62        Ok(()) => Ok(true),
63        Err(e)
64            if e.kind() == io::ErrorKind::NotFound
65                || e.kind() == io::ErrorKind::DirectoryNotEmpty =>
66        {
67            Ok(false)
68        }
69        Err(e) => Err(e),
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::device::CURRENT_DEVICE;
77
78    #[test]
79    fn cleanup_removes_bundled_files_but_keeps_user_files() {
80        let tmp = tempfile::tempdir().unwrap();
81        let install_dir = tmp.path().join(CURRENT_DEVICE.install_subdir());
82
83        std::fs::create_dir_all(install_dir.join("fonts")).unwrap();
84        std::fs::create_dir_all(install_dir.join("icons")).unwrap();
85        std::fs::create_dir_all(install_dir.join("libs")).unwrap();
86
87        std::fs::write(install_dir.join("fonts/NotoSans-Regular.ttf"), b"owned").unwrap();
88        std::fs::write(install_dir.join("fonts/custom.ttf"), b"user").unwrap();
89        std::fs::write(install_dir.join("icons/home.svg"), b"owned").unwrap();
90        std::fs::write(install_dir.join("libs/libfoo.so.1"), b"owned").unwrap();
91        std::fs::write(install_dir.join("Settings.toml"), b"user").unwrap();
92
93        clean_bundled_files(&install_dir).unwrap();
94
95        assert!(!install_dir.join("fonts/NotoSans-Regular.ttf").exists());
96        assert!(install_dir.join("fonts/custom.ttf").exists());
97        assert!(!install_dir.join("icons/home.svg").exists());
98        assert!(!install_dir.join("libs/libfoo.so.1").exists());
99        assert!(install_dir.join("Settings.toml").exists());
100    }
101}