metis_core/application/services/
filesystem.rs1use crate::{MetisError, Result};
2use sha2::{Digest, Sha256};
3use std::fs;
4use std::path::Path;
5
6pub struct FilesystemService;
9
10impl FilesystemService {
11 pub fn read_file<P: AsRef<Path>>(path: P) -> Result<String> {
13 fs::read_to_string(path).map_err(MetisError::Io)
14 }
15
16 pub fn write_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> {
18 if let Some(parent) = path.as_ref().parent() {
20 fs::create_dir_all(parent).map_err(MetisError::Io)?;
21 }
22
23 fs::write(path, content).map_err(MetisError::Io)
24 }
25
26 pub fn file_exists<P: AsRef<Path>>(path: P) -> bool {
28 path.as_ref().exists()
29 }
30
31 pub fn compute_file_hash<P: AsRef<Path>>(path: P) -> Result<String> {
33 let contents = Self::read_file(path)?;
34 let mut hasher = Sha256::new();
35 hasher.update(contents.as_bytes());
36 Ok(format!("{:x}", hasher.finalize()))
37 }
38
39 pub fn compute_content_hash(content: &str) -> String {
41 let mut hasher = Sha256::new();
42 hasher.update(content.as_bytes());
43 format!("{:x}", hasher.finalize())
44 }
45
46 pub fn get_file_mtime<P: AsRef<Path>>(path: P) -> Result<f64> {
48 let metadata = fs::metadata(path).map_err(MetisError::Io)?;
49 let mtime = metadata
50 .modified()
51 .map_err(MetisError::Io)?
52 .duration_since(std::time::UNIX_EPOCH)
53 .map_err(|_| MetisError::ValidationFailed {
54 message: "Invalid file modification time".to_string(),
55 })?;
56 Ok(mtime.as_secs_f64())
57 }
58
59 pub fn delete_file<P: AsRef<Path>>(path: P) -> Result<()> {
61 fs::remove_file(path).map_err(MetisError::Io)
62 }
63
64 pub fn find_markdown_files<P: AsRef<Path>>(dir: P) -> Result<Vec<String>> {
66 use walkdir::WalkDir;
67
68 let mut files = Vec::new();
69
70 for entry in WalkDir::new(dir).follow_links(true) {
71 let entry = entry
72 .map_err(|e| MetisError::Io(std::io::Error::other(format!("Walk error: {}", e))))?;
73
74 if entry.file_type().is_file() {
75 if let Some(path_str) = entry.path().to_str() {
76 if path_str.ends_with(".md") {
77 files.push(path_str.to_string());
78 }
79 }
80 }
81 }
82
83 Ok(files)
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use tempfile::tempdir;
91
92 #[test]
93 fn test_write_and_read_file() {
94 let temp_dir = tempdir().expect("Failed to create temp dir");
95 let file_path = temp_dir.path().join("test.md");
96
97 let content = "# Test Document\n\nThis is test content.";
98
99 FilesystemService::write_file(&file_path, content).expect("Failed to write file");
101
102 let read_content = FilesystemService::read_file(&file_path).expect("Failed to read file");
104 assert_eq!(content, read_content);
105
106 assert!(FilesystemService::file_exists(&file_path));
108 }
109
110 #[test]
111 fn test_compute_hashes() {
112 let content = "# Test Document\n\nThis is test content.";
113
114 let hash1 = FilesystemService::compute_content_hash(content);
116 let hash2 = FilesystemService::compute_content_hash(content);
117 assert_eq!(hash1, hash2); let different_content = "# Different Document\n\nThis is different content.";
120 let hash3 = FilesystemService::compute_content_hash(different_content);
121 assert_ne!(hash1, hash3); let temp_dir = tempdir().expect("Failed to create temp dir");
125 let file_path = temp_dir.path().join("test.md");
126 FilesystemService::write_file(&file_path, content).expect("Failed to write file");
127
128 let file_hash =
129 FilesystemService::compute_file_hash(&file_path).expect("Failed to compute file hash");
130 assert_eq!(hash1, file_hash); }
132
133 #[test]
134 fn test_file_operations() {
135 let temp_dir = tempdir().expect("Failed to create temp dir");
136 let file_path = temp_dir.path().join("subdir").join("test.md");
137
138 let content = "# Test Document";
139
140 FilesystemService::write_file(&file_path, content).expect("Failed to write file");
142 assert!(FilesystemService::file_exists(&file_path));
143
144 let mtime = FilesystemService::get_file_mtime(&file_path).expect("Failed to get mtime");
146 assert!(mtime > 0.0);
147
148 FilesystemService::delete_file(&file_path).expect("Failed to delete file");
150 assert!(!FilesystemService::file_exists(&file_path));
151 }
152
153 #[test]
154 fn test_find_markdown_files() {
155 let temp_dir = tempdir().expect("Failed to create temp dir");
156 let base_path = temp_dir.path();
157
158 let files = vec![
160 "doc1.md",
161 "subdir/doc2.md",
162 "subdir/nested/doc3.md",
163 "not_markdown.txt",
164 ];
165
166 for file in &files {
167 let file_path = base_path.join(file);
168 FilesystemService::write_file(&file_path, "# Test").expect("Failed to write file");
169 }
170
171 let found_files = FilesystemService::find_markdown_files(base_path)
173 .expect("Failed to find markdown files");
174
175 assert_eq!(found_files.len(), 3);
177
178 for file in &found_files {
180 assert!(file.ends_with(".md"));
181 }
182 }
183}