Skip to main content

zsfm_hub/
delete.rs

1use std::path::Path;
2
3use crate::log::log_status;
4
5/// Delete a model's cached files (canonical GGUF + config) and optionally an
6/// output GGUF. Shared by the `zsfm` CLI and the Python bindings so both
7/// report identical messages on stdout/stderr.
8pub fn delete_cached_model(canonical: &Path, output: Option<&Path>) -> anyhow::Result<()> {
9    let mut deleted_any = false;
10
11    if let Some(cache_dir) = canonical.parent() {
12        if cache_dir.exists() {
13            let file_count = walk_file_count(cache_dir);
14            std::fs::remove_dir_all(cache_dir)?;
15            log_status(&format!(
16                "Deleted cache directory {} ({} files)",
17                cache_dir.display(),
18                file_count
19            ));
20            deleted_any = true;
21        } else if canonical.exists() {
22            std::fs::remove_file(canonical)?;
23            log_status(&format!("Deleted {}", canonical.display()));
24            deleted_any = true;
25        } else {
26            log_status(&format!(
27                "No cache found at {} (already deleted?)",
28                cache_dir.display()
29            ));
30        }
31    } else if canonical.exists() {
32        std::fs::remove_file(canonical)?;
33        log_status(&format!("Deleted {}", canonical.display()));
34        deleted_any = true;
35    }
36
37    if let Some(out) = output {
38        if out.exists() {
39            std::fs::remove_file(out)?;
40            log_status(&format!("Deleted output {}", out.display()));
41            deleted_any = true;
42        } else {
43            log_status(&format!(
44                "Output file not found: {} (already deleted?)",
45                out.display()
46            ));
47        }
48    }
49
50    if !deleted_any {
51        log_status("Nothing to delete.");
52    }
53    Ok(())
54}
55
56fn walk_file_count(dir: &Path) -> usize {
57    let mut count = 0;
58    if let Ok(entries) = std::fs::read_dir(dir) {
59        for entry in entries.flatten() {
60            let path = entry.path();
61            if path.is_dir() {
62                count += walk_file_count(&path);
63            } else {
64                count += 1;
65            }
66        }
67    }
68    count
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use std::fs;
75
76    #[test]
77    fn deletes_cache_dir_and_output() {
78        let base = std::env::temp_dir().join(format!("zsfm-del-{}", std::process::id()));
79        let _ = fs::remove_dir_all(&base);
80        let cache_dir = base.join("owner__repo");
81        fs::create_dir_all(&cache_dir).unwrap();
82        let canonical = cache_dir.join("model-f32.gguf");
83        fs::write(&canonical, b"gguf").unwrap();
84        let out = base.join("out.gguf");
85        fs::write(&out, b"out").unwrap();
86        delete_cached_model(&canonical, Some(&out)).unwrap();
87        assert!(!cache_dir.exists());
88        assert!(!out.exists());
89        let _ = fs::remove_dir_all(&base);
90    }
91
92    #[test]
93    fn missing_cache_is_ok() {
94        let base = std::env::temp_dir().join(format!("zsfm-del-miss-{}", std::process::id()));
95        let canonical = base.join("owner__repo").join("model-f32.gguf");
96        delete_cached_model(&canonical, None).unwrap();
97    }
98}