use crate::{MetisError, Result};
use sha2::{Digest, Sha256};
use std::fs;
use std::path::Path;
pub struct FilesystemService;
impl FilesystemService {
pub fn read_file<P: AsRef<Path>>(path: P) -> Result<String> {
fs::read_to_string(path).map_err(MetisError::Io)
}
pub fn write_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> {
if let Some(parent) = path.as_ref().parent() {
fs::create_dir_all(parent).map_err(MetisError::Io)?;
}
fs::write(path, content).map_err(MetisError::Io)
}
pub fn file_exists<P: AsRef<Path>>(path: P) -> bool {
path.as_ref().exists()
}
pub fn compute_file_hash<P: AsRef<Path>>(path: P) -> Result<String> {
let contents = Self::read_file(path)?;
let mut hasher = Sha256::new();
hasher.update(contents.as_bytes());
Ok(format!("{:x}", hasher.finalize()))
}
pub fn compute_content_hash(content: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(content.as_bytes());
format!("{:x}", hasher.finalize())
}
pub fn get_file_mtime<P: AsRef<Path>>(path: P) -> Result<f64> {
let metadata = fs::metadata(path).map_err(MetisError::Io)?;
let mtime = metadata
.modified()
.map_err(MetisError::Io)?
.duration_since(std::time::UNIX_EPOCH)
.map_err(|_| MetisError::ValidationFailed {
message: "Invalid file modification time".to_string(),
})?;
Ok(mtime.as_secs_f64())
}
pub fn delete_file<P: AsRef<Path>>(path: P) -> Result<()> {
fs::remove_file(path).map_err(MetisError::Io)
}
pub fn find_markdown_files<P: AsRef<Path>>(dir: P) -> Result<Vec<String>> {
use walkdir::WalkDir;
let mut files = Vec::new();
for entry in WalkDir::new(dir).follow_links(true) {
let entry = entry
.map_err(|e| MetisError::Io(std::io::Error::other(format!("Walk error: {}", e))))?;
if entry.file_type().is_file() {
if let Some(path_str) = entry.path().to_str() {
if path_str.ends_with(".md") {
files.push(path_str.to_string());
}
}
}
}
Ok(files)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_write_and_read_file() {
let temp_dir = tempdir().expect("Failed to create temp dir");
let file_path = temp_dir.path().join("test.md");
let content = "# Test Document\n\nThis is test content.";
FilesystemService::write_file(&file_path, content).expect("Failed to write file");
let read_content = FilesystemService::read_file(&file_path).expect("Failed to read file");
assert_eq!(content, read_content);
assert!(FilesystemService::file_exists(&file_path));
}
#[test]
fn test_compute_hashes() {
let content = "# Test Document\n\nThis is test content.";
let hash1 = FilesystemService::compute_content_hash(content);
let hash2 = FilesystemService::compute_content_hash(content);
assert_eq!(hash1, hash2);
let different_content = "# Different Document\n\nThis is different content.";
let hash3 = FilesystemService::compute_content_hash(different_content);
assert_ne!(hash1, hash3);
let temp_dir = tempdir().expect("Failed to create temp dir");
let file_path = temp_dir.path().join("test.md");
FilesystemService::write_file(&file_path, content).expect("Failed to write file");
let file_hash =
FilesystemService::compute_file_hash(&file_path).expect("Failed to compute file hash");
assert_eq!(hash1, file_hash); }
#[test]
fn test_file_operations() {
let temp_dir = tempdir().expect("Failed to create temp dir");
let file_path = temp_dir.path().join("subdir").join("test.md");
let content = "# Test Document";
FilesystemService::write_file(&file_path, content).expect("Failed to write file");
assert!(FilesystemService::file_exists(&file_path));
let mtime = FilesystemService::get_file_mtime(&file_path).expect("Failed to get mtime");
assert!(mtime > 0.0);
FilesystemService::delete_file(&file_path).expect("Failed to delete file");
assert!(!FilesystemService::file_exists(&file_path));
}
#[test]
fn test_find_markdown_files() {
let temp_dir = tempdir().expect("Failed to create temp dir");
let base_path = temp_dir.path();
let files = vec![
"doc1.md",
"subdir/doc2.md",
"subdir/nested/doc3.md",
"not_markdown.txt",
];
for file in &files {
let file_path = base_path.join(file);
FilesystemService::write_file(&file_path, "# Test").expect("Failed to write file");
}
let found_files = FilesystemService::find_markdown_files(base_path)
.expect("Failed to find markdown files");
assert_eq!(found_files.len(), 3);
for file in &found_files {
assert!(file.ends_with(".md"));
}
}
}