use chrono::{DateTime, Local};
use std::path::Path;
use std::time::SystemTime;
use tokio::fs;
use tracing::warn;
#[derive(Debug, Clone)]
pub struct DirectoryEntry {
pub name: String,
pub size: u64,
pub modified: SystemTime,
pub is_dir: bool,
}
pub fn format_file_size(size: u64) -> String {
const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
const THRESHOLD: f64 = 1024.0;
if size == 0 {
return "0 B".to_string();
}
let mut size_f = size as f64;
let mut unit_index = 0;
while size_f >= THRESHOLD && unit_index < UNITS.len() - 1 {
size_f /= THRESHOLD;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", size, UNITS[unit_index])
} else {
format!("{:.1} {}", size_f, UNITS[unit_index])
}
}
pub fn format_timestamp(timestamp: SystemTime) -> String {
let datetime: DateTime<Local> = timestamp.into();
datetime.format("%Y-%m-%d %H:%M:%S").to_string()
}
pub async fn collect_directory_entries(
dir_path: &Path,
) -> Result<Vec<DirectoryEntry>, std::io::Error> {
let mut entries = Vec::new();
let mut read_dir = fs::read_dir(dir_path).await?;
while let Some(entry) = read_dir.next_entry().await? {
let metadata = match entry.metadata().await {
Ok(metadata) => metadata,
Err(err) => {
warn!(
"failed to read metadata for {}: {}",
entry.path().display(),
err
);
continue;
}
};
let file_name = entry.file_name();
let modified = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
entries.push(DirectoryEntry {
name: file_name.to_string_lossy().into_owned(),
size: metadata.len(),
modified,
is_dir: metadata.is_dir(),
});
}
Ok(entries)
}
pub fn escape_html(input: &str) -> String {
input
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
pub fn get_mime_type(file_path: &Path) -> String {
mime_guess::from_path(file_path)
.first_or_octet_stream()
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_size_formatting() {
assert_eq!(format_file_size(0), "0 B");
assert_eq!(format_file_size(512), "512 B");
assert_eq!(format_file_size(1024), "1.0 KiB");
assert_eq!(format_file_size(1536), "1.5 KiB");
assert_eq!(format_file_size(1048576), "1.0 MiB");
assert_eq!(format_file_size(1073741824), "1.0 GiB");
}
#[test]
fn test_html_escaping() {
assert_eq!(escape_html("normal text"), "normal text");
assert_eq!(escape_html("<script>"), "<script>");
assert_eq!(escape_html("a & b"), "a & b");
assert_eq!(escape_html("\"quoted\""), ""quoted"");
}
#[test]
fn test_mime_type_detection() {
assert_eq!(get_mime_type(Path::new("file.html")), "text/html");
assert_eq!(get_mime_type(Path::new("file.css")), "text/css");
assert_eq!(get_mime_type(Path::new("file.js")), "text/javascript");
assert_eq!(get_mime_type(Path::new("file.png")), "image/png");
assert_eq!(get_mime_type(Path::new("file.jpg")), "image/jpeg");
}
}