ai_lib/utils/
file.rs

1use crate::types::AiLibError;
2use std::fs;
3use std::io;
4use std::path::{Path, PathBuf};
5use std::time::{SystemTime, UNIX_EPOCH};
6
7/// Minimal file management helpers for multimodal flows.
8/// - save_temp_file: write bytes to a temp file and return the path
9/// - read_file: read bytes from a path
10/// - remove_file: delete a file
11/// - guess_mime_from_path: lightweight MIME guesser based on extension
12/// - validate_file: validate file exists and is readable
13/// - get_file_size: get file size in bytes
14/// - create_temp_dir: create a temporary directory
15
16/// Save bytes to a temporary file with a given prefix
17pub fn save_temp_file(prefix: &str, bytes: &[u8]) -> io::Result<PathBuf> {
18    let mut dir = std::env::temp_dir();
19    let ts = SystemTime::now()
20        .duration_since(UNIX_EPOCH)
21        .map(|d| d.as_nanos())
22        .unwrap_or(0);
23    let filename = format!("{}-{}.bin", prefix, ts);
24    dir.push(filename);
25    fs::write(&dir, bytes)?;
26    Ok(dir)
27}
28
29/// Read bytes from a file path
30pub fn read_file(path: &Path) -> io::Result<Vec<u8>> {
31    fs::read(path)
32}
33
34/// Remove a file from the filesystem
35pub fn remove_file(path: &Path) -> io::Result<()> {
36    fs::remove_file(path)
37}
38
39/// Guess MIME type from file extension
40pub fn guess_mime_from_path(path: &Path) -> &'static str {
41    if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
42        match ext.to_ascii_lowercase().as_str() {
43            "png" => "image/png",
44            "jpg" => "image/jpeg",
45            "jpeg" => "image/jpeg",
46            "gif" => "image/gif",
47            "webp" => "image/webp",
48            "mp3" => "audio/mpeg",
49            "wav" => "audio/wav",
50            "ogg" => "audio/ogg",
51            "mp4" => "video/mp4",
52            "avi" => "video/x-msvideo",
53            "mov" => "video/quicktime",
54            "pdf" => "application/pdf",
55            "txt" => "text/plain",
56            "md" => "text/markdown",
57            "json" => "application/json",
58            "xml" => "application/xml",
59            "html" => "text/html",
60            "css" => "text/css",
61            "js" => "application/javascript",
62            "zip" => "application/zip",
63            "tar" => "application/x-tar",
64            "gz" => "application/gzip",
65            _ => "application/octet-stream",
66        }
67    } else {
68        "application/octet-stream"
69    }
70}
71
72/// Validate that a file exists and is readable
73pub fn validate_file(path: &Path) -> Result<(), AiLibError> {
74    if !path.exists() {
75        return Err(AiLibError::FileError(format!(
76            "File does not exist: {}",
77            path.display()
78        )));
79    }
80
81    if !path.is_file() {
82        return Err(AiLibError::FileError(format!(
83            "Path is not a file: {}",
84            path.display()
85        )));
86    }
87
88    // Check if file is readable
89    fs::metadata(path)
90        .map_err(|e| AiLibError::FileError(format!("Cannot read file metadata: {}", e)))?;
91
92    Ok(())
93}
94
95/// Get file size in bytes
96pub fn get_file_size(path: &Path) -> Result<u64, AiLibError> {
97    let metadata = fs::metadata(path)
98        .map_err(|e| AiLibError::FileError(format!("Cannot read file metadata: {}", e)))?;
99    Ok(metadata.len())
100}
101
102/// Create a temporary directory with a given prefix
103pub fn create_temp_dir(prefix: &str) -> io::Result<PathBuf> {
104    let mut dir = std::env::temp_dir();
105    let ts = SystemTime::now()
106        .duration_since(UNIX_EPOCH)
107        .map(|d| d.as_nanos())
108        .unwrap_or(0);
109    let dirname = format!("{}-{}", prefix, ts);
110    dir.push(dirname);
111    fs::create_dir(&dir)?;
112    Ok(dir)
113}
114
115/// Check if a file is an image based on its extension
116pub fn is_image_file(path: &Path) -> bool {
117    if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
118        matches!(
119            ext.to_ascii_lowercase().as_str(),
120            "png" | "jpg" | "jpeg" | "gif" | "webp" | "bmp" | "tiff" | "svg"
121        )
122    } else {
123        false
124    }
125}
126
127/// Check if a file is an audio file based on its extension
128pub fn is_audio_file(path: &Path) -> bool {
129    if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
130        matches!(
131            ext.to_ascii_lowercase().as_str(),
132            "mp3" | "wav" | "ogg" | "flac" | "aac" | "m4a" | "wma"
133        )
134    } else {
135        false
136    }
137}
138
139/// Check if a file is a video file based on its extension
140pub fn is_video_file(path: &Path) -> bool {
141    if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
142        matches!(
143            ext.to_ascii_lowercase().as_str(),
144            "mp4" | "avi" | "mov" | "wmv" | "flv" | "mkv" | "webm" | "m4v"
145        )
146    } else {
147        false
148    }
149}
150
151/// Check if a file is a text file based on its extension
152pub fn is_text_file(path: &Path) -> bool {
153    if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
154        matches!(
155            ext.to_ascii_lowercase().as_str(),
156            "txt"
157                | "md"
158                | "json"
159                | "xml"
160                | "html"
161                | "css"
162                | "js"
163                | "rs"
164                | "py"
165                | "java"
166                | "cpp"
167                | "c"
168        )
169    } else {
170        false
171    }
172}
173
174/// Get file extension as lowercase string
175pub fn get_file_extension(path: &Path) -> Option<String> {
176    path.extension()
177        .and_then(|s| s.to_str())
178        .map(|s| s.to_ascii_lowercase())
179}
180
181/// Check if file size is within acceptable limits
182pub fn is_file_size_acceptable(path: &Path, max_size_mb: u64) -> Result<bool, AiLibError> {
183    let size = get_file_size(path)?;
184    let max_size_bytes = max_size_mb * 1024 * 1024;
185    Ok(size <= max_size_bytes)
186}