Skip to main content

tauri_plugin_stt/
paths.rs

1// Path management utilities based on tauri-plugin-sql patterns
2// See: https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/sql
3
4use std::fs::create_dir_all;
5use std::path::PathBuf;
6use tauri::{AppHandle, Manager, Runtime};
7
8use crate::error::Error;
9
10/// Default subdirectory for Whisper GGML models within app_data_dir
11const MODELS_SUBDIR: &str = "whisper-models";
12
13/// Gets the models directory for Whisper speech recognition models.
14///
15/// Uses `app_data_dir()` as base directory — these are large files
16/// (75 MB – 3 GB) that should persist across app updates.
17///
18/// # Example
19/// ```rust,ignore
20/// let models_dir = get_models_dir(&app)?;
21/// let model_path = models_dir.join("ggml-base.bin");
22/// ```
23#[allow(dead_code)]
24pub fn get_models_dir<R: Runtime>(app: &AppHandle<R>) -> Result<PathBuf, Error> {
25    let base_path = app
26        .path()
27        .app_data_dir()
28        .map_err(|e| Error::ConfigError(format!("Could not determine app data directory: {e}")))?;
29
30    let full_path = base_path.join(MODELS_SUBDIR);
31
32    create_dir_all(&full_path).map_err(|e| {
33        Error::ConfigError(format!(
34            "Could not create models directory {}: {}",
35            full_path.display(),
36            e
37        ))
38    })?;
39
40    Ok(full_path)
41}
42
43/// Gets a specific model's path.
44///
45/// # Arguments
46/// * `app` - The Tauri app handle
47/// * `model_name` - Name of the model file (e.g., "ggml-base.bin")
48#[allow(dead_code)]
49pub fn get_model_path<R: Runtime>(app: &AppHandle<R>, model_name: &str) -> Result<PathBuf, Error> {
50    validate_path(model_name)?;
51    let models_dir = get_models_dir(app)?;
52    Ok(models_dir.join(model_name))
53}
54
55/// Checks if a model file exists in the models directory.
56#[allow(dead_code)]
57pub fn model_exists<R: Runtime>(app: &AppHandle<R>, model_name: &str) -> Result<bool, Error> {
58    let model_path = get_model_path(app, model_name)?;
59    // Whisper models are single `.bin` files, not directories.
60    Ok(model_path.exists() && model_path.is_file())
61}
62
63/// Lists available model files in the models directory. Returns the
64/// raw filenames (`ggml-base.bin`, etc.); callers can match them
65/// against the catalogue surfaced by `list_models`.
66#[allow(dead_code)]
67pub fn list_available_models<R: Runtime>(app: &AppHandle<R>) -> Result<Vec<String>, Error> {
68    let models_dir = get_models_dir(app)?;
69    let mut models = Vec::new();
70
71    if let Ok(entries) = std::fs::read_dir(&models_dir) {
72        for entry in entries.flatten() {
73            if let Ok(metadata) = entry.metadata() {
74                // Whisper models are single `.bin` files.
75                if metadata.is_file() {
76                    if let Some(name) = entry.file_name().to_str() {
77                        if name.ends_with(".bin") {
78                            models.push(name.to_string());
79                        }
80                    }
81                }
82            }
83        }
84    }
85
86    Ok(models)
87}
88
89/// Validates that a path doesn't contain path traversal attacks.
90#[allow(dead_code)]
91pub fn validate_path(path: &str) -> Result<(), Error> {
92    let path_buf = PathBuf::from(path);
93
94    for component in path_buf.components() {
95        if let std::path::Component::ParentDir = component {
96            return Err(Error::ConfigError(
97                "Path traversal not allowed (contains '..')".to_string(),
98            ));
99        }
100    }
101
102    Ok(())
103}