ai_lib/utils/
file.rs

1use std::fs;
2use std::io;
3use std::path::{Path, PathBuf};
4use std::time::{SystemTime, UNIX_EPOCH};
5use crate::types::AiLibError;
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!("File does not exist: {}", path.display())));
76    }
77    
78    if !path.is_file() {
79        return Err(AiLibError::FileError(format!("Path is not a file: {}", path.display())));
80    }
81    
82    // Check if file is readable
83    fs::metadata(path)
84        .map_err(|e| AiLibError::FileError(format!("Cannot read file metadata: {}", e)))?;
85    
86    Ok(())
87}
88
89/// Get file size in bytes
90pub fn get_file_size(path: &Path) -> Result<u64, AiLibError> {
91    let metadata = fs::metadata(path)
92        .map_err(|e| AiLibError::FileError(format!("Cannot read file metadata: {}", e)))?;
93    Ok(metadata.len())
94}
95
96/// Create a temporary directory with a given prefix
97pub fn create_temp_dir(prefix: &str) -> io::Result<PathBuf> {
98    let mut dir = std::env::temp_dir();
99    let ts = SystemTime::now()
100        .duration_since(UNIX_EPOCH)
101        .map(|d| d.as_nanos())
102        .unwrap_or(0);
103    let dirname = format!("{}-{}", prefix, ts);
104    dir.push(dirname);
105    fs::create_dir(&dir)?;
106    Ok(dir)
107}
108
109/// Check if a file is an image based on its extension
110pub fn is_image_file(path: &Path) -> bool {
111    if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
112        matches!(ext.to_ascii_lowercase().as_str(), 
113            "png" | "jpg" | "jpeg" | "gif" | "webp" | "bmp" | "tiff" | "svg"
114        )
115    } else {
116        false
117    }
118}
119
120/// Check if a file is an audio file based on its extension
121pub fn is_audio_file(path: &Path) -> bool {
122    if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
123        matches!(ext.to_ascii_lowercase().as_str(), 
124            "mp3" | "wav" | "ogg" | "flac" | "aac" | "m4a" | "wma"
125        )
126    } else {
127        false
128    }
129}
130
131/// Check if a file is a video file based on its extension
132pub fn is_video_file(path: &Path) -> bool {
133    if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
134        matches!(ext.to_ascii_lowercase().as_str(), 
135            "mp4" | "avi" | "mov" | "wmv" | "flv" | "mkv" | "webm" | "m4v"
136        )
137    } else {
138        false
139    }
140}
141
142/// Check if a file is a text file based on its extension
143pub fn is_text_file(path: &Path) -> bool {
144    if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
145        matches!(ext.to_ascii_lowercase().as_str(), 
146            "txt" | "md" | "json" | "xml" | "html" | "css" | "js" | "rs" | "py" | "java" | "cpp" | "c"
147        )
148    } else {
149        false
150    }
151}
152
153/// Get file extension as lowercase string
154pub fn get_file_extension(path: &Path) -> Option<String> {
155    path.extension()
156        .and_then(|s| s.to_str())
157        .map(|s| s.to_ascii_lowercase())
158}
159
160/// Check if file size is within acceptable limits
161pub fn is_file_size_acceptable(path: &Path, max_size_mb: u64) -> Result<bool, AiLibError> {
162    let size = get_file_size(path)?;
163    let max_size_bytes = max_size_mb * 1024 * 1024;
164    Ok(size <= max_size_bytes)
165}