use std::io::{self, Write};
use std::path::Path;
pub const MAX_PREVIEW_SIZE: u64 = 512 * 1024;
pub const MAX_FILE_MENTION_SIZE: u64 = 1024 * 1024;
pub fn atomic_write(path: impl AsRef<Path>, content: &[u8]) -> io::Result<()> {
let path = path.as_ref();
let temp_path = path.with_extension("tmp.nika");
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let mut file = std::fs::File::create(&temp_path)?;
file.write_all(content)?;
file.flush()?;
file.sync_all()?;
std::fs::rename(&temp_path, path).inspect_err(|_| {
let _ = std::fs::remove_file(&temp_path);
})
}
pub async fn atomic_write_async(path: impl AsRef<Path>, content: &[u8]) -> io::Result<()> {
use tokio::io::AsyncWriteExt;
let path = path.as_ref();
let temp_path = path.with_extension("tmp.nika");
if let Some(parent) = path.parent() {
tokio::fs::create_dir_all(parent).await?;
}
let mut file = tokio::fs::File::create(&temp_path).await?;
file.write_all(content).await?;
file.flush().await?;
file.sync_all().await?;
match tokio::fs::rename(&temp_path, path).await {
Ok(()) => Ok(()),
Err(e) => {
let temp_clone = temp_path.clone();
tokio::spawn(async move {
let _ = tokio::fs::remove_file(temp_clone).await;
});
Err(e)
}
}
}
pub fn check_preview_size(path: impl AsRef<Path>) -> Result<u64, u64> {
let metadata = std::fs::metadata(path.as_ref()).map_err(|_| 0u64)?;
let size = metadata.len();
if size > MAX_PREVIEW_SIZE {
Err(size)
} else {
Ok(size)
}
}
pub fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.1} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.1} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.1} KB", bytes as f64 / KB as f64)
} else {
format!("{} bytes", bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_atomic_write_creates_file() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("test.txt");
atomic_write(&path, b"Hello, World!").unwrap();
assert!(path.exists());
let content = std::fs::read_to_string(&path).unwrap();
assert_eq!(content, "Hello, World!");
}
#[test]
fn test_atomic_write_no_temp_file_left() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("test.txt");
let temp_path = path.with_extension("tmp.nika");
atomic_write(&path, b"content").unwrap();
assert!(!temp_path.exists(), "Temp file should be cleaned up");
}
#[test]
fn test_atomic_write_creates_parent_dirs() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("nested").join("dir").join("file.txt");
atomic_write(&path, b"nested content").unwrap();
assert!(path.exists());
}
#[test]
fn test_atomic_write_overwrites_existing() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("test.txt");
atomic_write(&path, b"first").unwrap();
atomic_write(&path, b"second").unwrap();
let content = std::fs::read_to_string(&path).unwrap();
assert_eq!(content, "second");
}
#[tokio::test]
async fn test_atomic_write_async() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("async_test.txt");
atomic_write_async(&path, b"async content").await.unwrap();
assert!(path.exists());
let content = std::fs::read_to_string(&path).unwrap();
assert_eq!(content, "async content");
}
#[test]
fn test_check_preview_size_ok() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("small.txt");
std::fs::write(&path, "small content").unwrap();
let result = check_preview_size(&path);
assert!(result.is_ok());
}
#[test]
fn test_check_preview_size_too_large() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("large.txt");
let large_content = "x".repeat((MAX_PREVIEW_SIZE + 1) as usize);
std::fs::write(&path, large_content).unwrap();
let result = check_preview_size(&path);
assert!(result.is_err());
}
#[test]
fn test_format_size() {
assert_eq!(format_size(0), "0 bytes");
assert_eq!(format_size(512), "512 bytes");
assert_eq!(format_size(1024), "1.0 KB");
assert_eq!(format_size(1536), "1.5 KB");
assert_eq!(format_size(1_048_576), "1.0 MB");
assert_eq!(format_size(1_073_741_824), "1.0 GB");
}
}