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