Skip to main content

volition_core/tools/
fs.rs

1// volition-agent-core/src/tools/fs.rs
2
3use 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}