Skip to main content

whisper_macos_cli/model/
storage.rs

1use std::path::PathBuf;
2
3use directories::ProjectDirs;
4
5use super::registry::ModelInfo;
6use crate::error::Error;
7
8fn project_dirs() -> Result<ProjectDirs, Error> {
9    ProjectDirs::from("", "", "whisper-macos-cli")
10        .ok_or_else(|| Error::Config("cannot determine application data directory".into()))
11}
12
13pub fn models_dir() -> Result<PathBuf, Error> {
14    let dirs = project_dirs()?;
15    let path = dirs.data_dir().join("models");
16    std::fs::create_dir_all(&path)?;
17    Ok(path)
18}
19
20pub fn model_path(model: &ModelInfo) -> Result<PathBuf, Error> {
21    Ok(models_dir()?.join(model.filename))
22}
23
24pub fn is_model_downloaded(model: &ModelInfo) -> Result<bool, Error> {
25    let path = model_path(model)?;
26    match std::fs::metadata(&path) {
27        Ok(meta) => Ok(meta.len() >= model.min_size_bytes),
28        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
29        Err(e) => Err(Error::Io(e)),
30    }
31}
32
33pub fn remove_model(model: &ModelInfo) -> Result<(), Error> {
34    let path = model_path(model)?;
35    match std::fs::remove_file(&path) {
36        Ok(()) => Ok(()),
37        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
38        Err(e) => Err(Error::Io(e)),
39    }
40}
41
42pub fn model_temp_path(model: &ModelInfo) -> Result<PathBuf, Error> {
43    Ok(model_path(model)?.with_extension("bin.tmp"))
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use crate::model::registry;
50
51    #[test]
52    fn model_path_ends_with_filename() {
53        let model = &registry::all_models()[0];
54        let path = model_path(model).unwrap();
55        assert!(path.ends_with(model.filename));
56    }
57
58    #[test]
59    fn model_temp_path_uses_bin_tmp_suffix() {
60        let model = &registry::all_models()[0];
61        let path = model_temp_path(model).unwrap();
62        let path_str = path.to_string_lossy();
63        assert!(
64            path_str.ends_with(".bin.tmp"),
65            "expected .bin.tmp suffix, got {path_str}"
66        );
67    }
68
69    #[test]
70    fn is_model_downloaded_returns_false_when_missing() {
71        let model = &registry::all_models()[0];
72        let path = model_path(model).unwrap();
73        let _ = std::fs::remove_file(&path);
74        let result = is_model_downloaded(model).unwrap();
75        assert!(!result);
76    }
77
78    #[test]
79    fn remove_model_is_idempotent_when_missing() {
80        let model = &registry::all_models()[0];
81        let path = model_path(model).unwrap();
82        let _ = std::fs::remove_file(&path);
83        assert!(remove_model(model).is_ok());
84    }
85
86    #[test]
87    fn models_dir_creates_directory() {
88        let path = models_dir().unwrap();
89        assert!(path.exists());
90        assert!(path.is_dir());
91        assert!(path.ends_with("models"));
92    }
93}