dev_tool/
file_util.rs

1//! 文件工具
2use chrono::{DateTime, Local};
3use std::fs::{self, File};
4use std::io::{self, Read, Write};
5use std::path::Path;
6
7/// 文件工具结构体
8pub struct FileUtil;
9
10impl FileUtil {
11    /// 罗列指定路径下的子目录及文件。
12    ///
13    /// 参数 `path` 是要罗列内容的目标路径。
14    /// 参数 `recurse` 决定是否以递归方式罗列子文件夹内的内容。
15    ///
16    /// 返回值是一个包含所有子目录及文件路径的 `Vec<String>`。
17    /// 如果在读取目录时发生错误,将返回一个空的 `Vec`。
18    pub fn list(path: &Path, recurse: bool) -> Vec<String> {
19        let mut result = Vec::new();
20        let entries = match fs::read_dir(path) {
21            Ok(entries) => entries,
22            Err(_) => return result,
23        };
24
25        for entry in entries {
26            let entry = match entry {
27                Ok(entry) => entry,
28                Err(_) => continue,
29            };
30            let path = entry.path();
31            if path.is_dir() {
32                if recurse {
33                    result.extend(Self::list(&path, true));
34                }
35                result.push(path.to_string_lossy().to_string());
36            } else {
37                result.push(path.to_string_lossy().to_string());
38            }
39        }
40        result
41    }
42
43    /// 获取指定文件的元数据信息
44    ///
45    /// 该函数封装了标准库中的 fs::metadata 方法,用于获取文件的基本信息,
46    /// 包括文件大小、创建时间、修改时间、文件类型等元数据。
47    ///
48    /// # 参数
49    /// * `file_path` - 要获取元数据的文件路径字符串引用
50    ///
51    /// # 返回值
52    /// 返回一个 io::Result<std::fs::Metadata> 类型:
53    /// * 成功时返回包含文件元数据的 Metadata 对象
54    /// * 失败时返回对应的 IO 错误信息
55    ///
56    /// # 错误处理
57    /// 当文件不存在或没有访问权限时,会返回相应的 IO 错误
58    pub fn metadata(file_path: &str) -> io::Result<std::fs::Metadata> {
59        fs::metadata(file_path)
60    }
61
62    /// 获取指定文件的最后修改时间
63    ///
64    /// # 参数
65    /// * `file_path` - 要查询的文件路径字符串引用
66    ///
67    /// # 返回值
68    /// 返回 Result<String, io::Error> 类型:
69    /// * 成功时返回格式化的时间字符串,格式为 "YYYY-MM-DD HH:MM:SS"
70    /// * 失败时返回对应的 IO 错误
71    ///
72    /// # 错误处理
73    /// 当文件不存在或无法访问时,会返回相应的 IO 错误
74    pub fn last_midified(file_path: &str) -> io::Result<String> {
75        // 获取文件元数据
76        let metadata = fs::metadata(file_path)?;
77        // 提取文件最后修改时间
78        let last_modified = metadata.modified().unwrap();
79        // 将系统时间转换为本地时间格式
80        let last_modified: DateTime<Local> = last_modified.into();
81        // 格式化时间为指定字符串格式并返回
82        Ok(last_modified.format("%Y-%m-%d %H:%M:%S").to_string())
83    }
84
85    /// 读取本地文件内容并以UTF - 8字符串形式返回。
86    ///
87    /// 参数 `path` 是要读取的文件路径。
88    ///
89    /// 如果文件成功打开并读取,将返回包含文件内容的 `String`。
90    /// 如果在打开或读取文件时发生I/O错误,将返回对应的 `io::Error`。
91    pub fn read_string(path: &Path) -> Result<String, io::Error> {
92        let mut file = File::open(path)?;
93        let mut contents = String::new();
94        file.read_to_string(&mut contents)?;
95        Ok(contents)
96    }
97
98    /// 读取本地文件内容并以字节数组形式返回。
99    ///
100    /// 参数 `path` 是要读取的文件路径。
101    ///
102    /// 如果文件成功打开并读取,将返回包含文件内容的 `Vec<u8>`。
103    /// 如果在打开或读取文件时发生I/O错误,将返回对应的 `io::Error`。
104    pub fn read_bytes(path: &Path) -> Result<Vec<u8>, io::Error> {
105        let mut file = File::open(path)?;
106        let mut contents = Vec::new();
107        file.read_to_end(&mut contents)?;
108        Ok(contents)
109    }
110
111    /// 将给定的字符串内容以覆盖方式写入到指定路径的文本文件。
112    ///
113    /// 参数 `path` 是要写入的文件路径。
114    /// 参数 `content` 是要写入文件的字符串内容。
115    ///
116    /// 如果文件成功创建并写入内容,将返回 `Ok(())`。
117    /// 如果在创建或写入文件时发生I/O错误,将返回对应的 `io::Error`。
118    pub fn write_string(path: &Path, content: String) -> io::Result<()> {
119        let mut file = File::create(path)?;
120        file.write_all(content.as_bytes())?;
121        Ok(())
122    }
123
124    /// 将给定的字符串内容以追加方式写入到指定路径的文件。
125    ///
126    /// 参数 `path` 是要写入的文件路径。
127    /// 参数 `content` 是要追加到文件的字符串内容。
128    ///
129    /// 如果文件成功打开并追加内容,将返回 `Ok(())`。
130    /// 如果在打开或写入文件时发生I/O错误,将返回对应的 `io::Error`。
131    pub fn append_string(path: &Path, content: String) -> io::Result<()> {
132        let mut file = File::options().append(true).open(path)?;
133        file.write_all(content.as_bytes())?;
134        Ok(())
135    }
136
137    /// 将给定的字节数组内容写入到指定路径的文件。
138    ///
139    /// 参数 `path` 是要写入的文件路径。
140    /// 参数 `bytes` 是要写入文件的字节数组内容。
141    ///
142    /// 如果文件成功创建并写入内容,将返回 `Ok(())`。
143    /// 如果在创建或写入文件时发生I/O错误,将返回对应的 `io::Error`。
144    pub fn write_bytes(path: &Path, bytes: &[u8]) -> io::Result<()> {
145        let mut file = File::create(path)?;
146        file.write_all(bytes)?;
147        Ok(())
148    }
149
150    /// 创建目录及其所有必要的父目录
151    pub fn create_dir_with_parents(dir_path: &str) -> io::Result<()> {
152        fs::create_dir_all(dir_path)
153    }
154
155    /// 删除单个文件
156    pub fn delete_file(file_path: &str) -> io::Result<()> {
157        fs::remove_file(file_path)
158    }
159
160    /// 删除含有子文件的目录
161    pub fn delete_directory(dir_path: &str) -> io::Result<()> {
162        fs::remove_dir_all(dir_path)
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    static ROOT_DIR: &str = "d:/tmp/";
170
171    #[test]
172    fn test_list() {
173        let temp_dir = Path::new(ROOT_DIR);
174        let sub_dir = temp_dir.join("sub_dir");
175        std::fs::create_dir(&sub_dir).expect("Failed to create sub dir");
176        let file_path = sub_dir.join("test_file.txt");
177        std::fs::File::create(&file_path).expect("Failed to create file");
178
179        let result = FileUtil::list(temp_dir, true);
180        assert!(result.contains(&file_path.to_string_lossy().to_string()));
181    }
182
183    #[test]
184    fn test_read_string() -> io::Result<()> {
185        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
186        println!("{:?}", temp_path);
187        let mut temp_file = File::options()
188            .write(true)
189            .create(true)
190            .open(temp_path.as_path())?;
191        let content = "test content".to_string();
192        temp_file
193            .write_all(content.as_bytes())
194            .expect("Failed to write to temp file");
195
196        let result = FileUtil::read_string(temp_path.as_path());
197        assert!(result.is_ok());
198        assert_eq!(result.unwrap(), content);
199        Ok(())
200    }
201
202    #[test]
203    fn test_read_bytes() {
204        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
205        let mut temp_file = File::options()
206            .write(true)
207            .open(temp_path.as_path())
208            .unwrap();
209        let content = [1, 2, 3];
210        temp_file
211            .write_all(&content)
212            .expect("Failed to write to temp file");
213
214        let result = FileUtil::read_bytes(temp_path.as_path());
215        assert!(result.is_ok());
216    }
217
218    #[test]
219    fn test_write_string() {
220        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
221        let _temp_file = File::options().write(true).open(&temp_path).unwrap();
222        let content = "test write content".to_string();
223
224        let result = FileUtil::write_string(&temp_path, content.clone());
225        assert!(result.is_ok());
226
227        let read_result = FileUtil::read_string(&temp_path);
228        assert!(read_result.is_ok());
229        assert_eq!(read_result.unwrap(), content);
230    }
231
232    #[test]
233    fn test_append_string() {
234        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
235        let _temp_file = File::open(&temp_path).unwrap();
236        let initial_content = "initial content".to_string();
237        let append_content = " appended content".to_string();
238        FileUtil::write_string(&temp_path, initial_content.clone())
239            .expect("Failed to write initial content");
240
241        let append_result = FileUtil::append_string(&temp_path, append_content.clone());
242        assert!(append_result.is_ok());
243
244        let read_result = FileUtil::read_string(&temp_path);
245        assert!(read_result.is_ok());
246        assert_eq!(read_result.unwrap(), initial_content + &append_content);
247    }
248
249    #[test]
250    fn test_write_bytes() {
251        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
252        let _temp_file = File::open(&temp_path).unwrap();
253        let content = [4, 5, 6];
254
255        let result = FileUtil::write_bytes(&temp_path, &content);
256        assert!(result.is_ok());
257
258        let read_result = FileUtil::read_bytes(&temp_path);
259        assert!(read_result.is_ok());
260        assert_eq!(read_result.unwrap(), content);
261    }
262}