volition_core/tools/
fs.rs1use std::fs;
4use std::path::Path;
5use std::time::UNIX_EPOCH;
6use tracing::info;
7
8#[derive(Debug)]
9pub struct FileInfo {
10 pub name: String,
11 pub path: String,
12 pub file_type: String,
13 pub size: Option<u64>,
14 pub modified: Option<u64>,
15}
16
17pub async fn read_file(relative_path: &str, working_dir: &Path) -> Result<String, String> {
18 let path = working_dir.join(relative_path);
19 info!("Reading file: {}", path.display());
20 fs::read_to_string(&path).map_err(|e| e.to_string())
21}
22
23pub async fn write_file(relative_path: &str, content: &str, working_dir: &Path) -> Result<String, String> {
24 let path = working_dir.join(relative_path);
25 info!("Writing file: {}", path.display());
26 fs::write(&path, content).map_err(|e| e.to_string())?;
27 Ok(format!("Successfully wrote to file: {}", relative_path))
28}
29
30pub fn list_directory_contents(path: &str, recursive: bool) -> Result<Vec<FileInfo>, String> {
31 fn list_recursive(base_path: &Path, current_path: &Path, files: &mut Vec<FileInfo>, recursive: bool) -> Result<(), String> {
32 let entries = match fs::read_dir(current_path) {
33 Ok(entries) => entries,
34 Err(e) => return Err(format!("Failed to read directory: {}", e)),
35 };
36
37 for entry in entries {
38 let entry = match entry {
39 Ok(entry) => entry,
40 Err(e) => {
41 eprintln!("Error reading directory entry: {}", e);
42 continue;
43 }
44 };
45
46 let metadata = match entry.metadata() {
47 Ok(meta) => meta,
48 Err(e) => {
49 eprintln!("Error reading metadata: {}", e);
50 continue;
51 }
52 };
53
54 let file_type = if metadata.is_dir() {
55 "directory"
56 } else if metadata.is_file() {
57 "file"
58 } else if metadata.is_symlink() {
59 "symlink"
60 } else {
61 "unknown"
62 };
63
64 let size = if metadata.is_file() {
65 Some(metadata.len())
66 } else {
67 None
68 };
69
70 let modified = metadata
71 .modified()
72 .ok()
73 .and_then(|time| time.duration_since(UNIX_EPOCH).ok())
74 .map(|duration| duration.as_secs());
75
76 let entry_path = entry.path();
77 let relative_path = entry_path.strip_prefix(base_path).unwrap_or(&entry_path);
78 let name = relative_path.to_string_lossy().into_owned();
79 let full_path = entry_path.to_string_lossy().into_owned();
80
81 let file_info = FileInfo {
82 name,
83 path: full_path,
84 file_type: file_type.to_string(),
85 size,
86 modified,
87 };
88
89 files.push(file_info);
90
91 if metadata.is_dir() && recursive {
92 list_recursive(base_path, &entry_path, files, recursive)?;
93 }
94 }
95 Ok(())
96 }
97
98 let path = Path::new(path);
99 if !path.exists() {
100 return Err(format!("Path does not exist: {}", path.display()));
101 }
102
103 if !path.is_dir() {
104 return Err(format!("Path is not a directory: {}", path.display()));
105 }
106
107 let mut files = Vec::new();
108 list_recursive(path, path, &mut files, recursive)?;
109 Ok(files)
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use std::fs::{self, File};
116 use tempfile::tempdir;
117
118 fn sort_lines(text: &str) -> Vec<&str> {
119 let mut lines: Vec<&str> = text.lines().collect();
120 lines.sort();
121 lines
122 }
123
124 #[test]
125 fn test_fs_list_basic() -> Result<(), String> {
126 let dir = tempdir().map_err(|e| e.to_string())?;
127 let wd = dir.path();
128 File::create(wd.join("f1.txt")).map_err(|e| e.to_string())?;
129 fs::create_dir(wd.join("sd")).map_err(|e| e.to_string())?;
130 File::create(wd.join("sd/f2.txt")).map_err(|e| e.to_string())?;
131 let output = list_directory_contents(wd.to_str().unwrap(), false)?;
132 let names: Vec<String> = output.iter().map(|f| f.name.clone()).collect();
133 assert_eq!(sort_lines(&names.join("\n")), sort_lines("f1.txt\nsd"));
134 Ok(())
135 }
136
137 #[test]
138 fn test_fs_list_depth() -> Result<(), String> {
139 let dir = tempdir().map_err(|e| e.to_string())?;
140 let wd = dir.path();
141 File::create(wd.join("f1.txt")).map_err(|e| e.to_string())?;
142 fs::create_dir(wd.join("sd")).map_err(|e| e.to_string())?;
143 File::create(wd.join("sd/f2.txt")).map_err(|e| e.to_string())?;
144 let output = list_directory_contents(wd.to_str().unwrap(), true)?;
145 let names: Vec<String> = output.iter().map(|f| f.name.clone()).collect();
146 let expected = format!("f1.txt\nsd\nsd{}f2.txt", std::path::MAIN_SEPARATOR);
147 assert_eq!(sort_lines(&names.join("\n")), sort_lines(&expected));
148 Ok(())
149 }
150}