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    /// 使用glob模式匹配文件路径
166    ///
167    /// 该函数接收一个glob模式字符串,搜索匹配的文件路径,并返回路径字符串的向量。
168    ///
169    /// # 参数
170    /// * `pattern` - glob模式字符串,用于匹配文件路径
171    ///
172    /// # 返回值
173    /// 返回一个Result类型,成功时包含匹配到的文件路径字符串向量,失败时包含IO错误
174    ///
175    /// # 示例
176    /// ```
177    /// let files = FileUtil::glob("D:/*/sub_dir").unwrap();
178    /// ```
179    pub fn glob(pattern: &str) -> io::Result<Vec<String>> {
180        let mut results = Vec::new();
181        let r = glob::glob(pattern);
182        if r.is_err() {
183            return Err(io::Error::new(io::ErrorKind::Other, r.err().unwrap().to_string()));
184        }
185        // 遍历glob模式匹配到的所有路径
186        for entry in r.unwrap() {
187            match entry {
188                Ok(path) => {
189                    let tmp = path.into_os_string().into_string().unwrap();
190                    let tmp = tmp.replace("\\", "/");
191                    results.push(tmp);
192                },
193                Err(e) => println!("{:?}", e),
194            }
195        }
196        Ok(results)
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    static ROOT_DIR: &str = "d:/tmp/";
204
205    #[test]
206    fn test_glob() {
207        let xx = FileUtil::glob("D:/*/sub_dir").unwrap();
208        println!("{:?}", xx);
209    }
210
211    #[test]
212    fn test_list() {
213        let temp_dir = Path::new(ROOT_DIR);
214        let sub_dir = temp_dir.join("sub_dir");
215        std::fs::create_dir(&sub_dir).expect("Failed to create sub dir");
216        let file_path = sub_dir.join("test_file.txt");
217        std::fs::File::create(&file_path).expect("Failed to create file");
218
219        let result = FileUtil::list(temp_dir, true);
220        assert!(result.contains(&file_path.to_string_lossy().to_string()));
221    }
222
223    #[test]
224    fn test_read_string() -> io::Result<()> {
225        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
226        println!("{:?}", temp_path);
227        let mut temp_file = File::options()
228            .write(true)
229            .create(true)
230            .open(temp_path.as_path())?;
231        let content = "test content".to_string();
232        temp_file
233            .write_all(content.as_bytes())
234            .expect("Failed to write to temp file");
235
236        let result = FileUtil::read_string(temp_path.as_path());
237        assert!(result.is_ok());
238        assert_eq!(result.unwrap(), content);
239        Ok(())
240    }
241
242    #[test]
243    fn test_read_bytes() {
244        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
245        let mut temp_file = File::options()
246            .write(true)
247            .open(temp_path.as_path())
248            .unwrap();
249        let content = [1, 2, 3];
250        temp_file
251            .write_all(&content)
252            .expect("Failed to write to temp file");
253
254        let result = FileUtil::read_bytes(temp_path.as_path());
255        assert!(result.is_ok());
256    }
257
258    #[test]
259    fn test_write_string() {
260        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
261        let _temp_file = File::options().write(true).open(&temp_path).unwrap();
262        let content = "test write content".to_string();
263
264        let result = FileUtil::write_string(&temp_path, content.clone());
265        assert!(result.is_ok());
266
267        let read_result = FileUtil::read_string(&temp_path);
268        assert!(read_result.is_ok());
269        assert_eq!(read_result.unwrap(), content);
270    }
271
272    #[test]
273    fn test_append_string() {
274        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
275        let _temp_file = File::open(&temp_path).unwrap();
276        let initial_content = "initial content".to_string();
277        let append_content = " appended content".to_string();
278        FileUtil::write_string(&temp_path, initial_content.clone())
279            .expect("Failed to write initial content");
280
281        let append_result = FileUtil::append_string(&temp_path, append_content.clone());
282        assert!(append_result.is_ok());
283
284        let read_result = FileUtil::read_string(&temp_path);
285        assert!(read_result.is_ok());
286        assert_eq!(read_result.unwrap(), initial_content + &append_content);
287    }
288
289    #[test]
290    fn test_write_bytes() {
291        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
292        let _temp_file = File::open(&temp_path).unwrap();
293        let content = [4, 5, 6];
294
295        let result = FileUtil::write_bytes(&temp_path, &content);
296        assert!(result.is_ok());
297
298        let read_result = FileUtil::read_bytes(&temp_path);
299        assert!(read_result.is_ok());
300        assert_eq!(read_result.unwrap(), content);
301    }
302}