tauri_plugin_media_toolkit/
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 media cache within app_cache_dir
11const MEDIA_CACHE_SUBDIR: &str = "media-cache";
12
13/// Default subdirectory for processed media within app_data_dir
14const MEDIA_OUTPUT_SUBDIR: &str = "media";
15
16/// Gets the media cache directory for temporary processing files.
17///
18/// Uses `app_cache_dir()` as base directory - files here can be deleted anytime.
19///
20/// # Example
21/// ```rust,ignore
22/// let cache_dir = get_media_cache_dir(&app)?;
23/// let temp_file = cache_dir.join("temp_output.mp4");
24/// ```
25pub fn get_media_cache_dir<R: Runtime>(app: &AppHandle<R>) -> Result<PathBuf, Error> {
26    let base_path = app
27        .path()
28        .app_cache_dir()
29        .map_err(|e| Error::InvalidPath(format!("Could not determine app cache directory: {e}")))?;
30
31    let full_path = base_path.join(MEDIA_CACHE_SUBDIR);
32
33    create_dir_all(&full_path).map_err(|e| {
34        Error::InvalidPath(format!(
35            "Could not create cache directory {}: {}",
36            full_path.display(),
37            e
38        ))
39    })?;
40
41    Ok(full_path)
42}
43
44/// Gets the media output directory for permanent processed files.
45///
46/// Uses `app_data_dir()` as base directory for user data.
47pub fn get_media_output_dir<R: Runtime>(app: &AppHandle<R>) -> Result<PathBuf, Error> {
48    let base_path = app
49        .path()
50        .app_data_dir()
51        .map_err(|e| Error::InvalidPath(format!("Could not determine app data directory: {e}")))?;
52
53    let full_path = base_path.join(MEDIA_OUTPUT_SUBDIR);
54
55    create_dir_all(&full_path).map_err(|e| {
56        Error::InvalidPath(format!(
57            "Could not create output directory {}: {}",
58            full_path.display(),
59            e
60        ))
61    })?;
62
63    Ok(full_path)
64}
65
66/// Validates that a path doesn't contain path traversal attacks.
67pub fn validate_path(path: &str) -> Result<(), Error> {
68    let path_buf = PathBuf::from(path);
69
70    for component in path_buf.components() {
71        if let std::path::Component::ParentDir = component {
72            return Err(Error::InvalidPath(
73                "Path traversal not allowed (contains '..')".to_string(),
74            ));
75        }
76    }
77
78    Ok(())
79}
80
81/// Cleans up old cache files (older than the specified duration).
82///
83/// This should be called on plugin initialization to prevent cache buildup.
84
85pub fn cleanup_old_cache<R: Runtime>(app: &AppHandle<R>, max_age_hours: u64) -> Result<u64, Error> {
86    use std::time::{Duration, SystemTime};
87
88    let cache_dir = get_media_cache_dir(app)?;
89    let max_age = Duration::from_secs(max_age_hours * 3600);
90    let now = SystemTime::now();
91    let mut deleted_count = 0u64;
92
93    if let Ok(entries) = std::fs::read_dir(&cache_dir) {
94        for entry in entries.flatten() {
95            if let Ok(metadata) = entry.metadata() {
96                if let Ok(modified) = metadata.modified() {
97                    if let Ok(age) = now.duration_since(modified) {
98                        if age > max_age && metadata.is_file() {
99                            if std::fs::remove_file(entry.path()).is_ok() {
100                                deleted_count += 1;
101                            }
102                        }
103                    }
104                }
105            }
106        }
107    }
108
109    Ok(deleted_count)
110}