dev_tool/
file_util.rs

1//! 文件工具
2use chrono::{DateTime, Local};
3use std::fs::{self, File, DirEntry};
4use std::io::{self, BufRead, BufReader, Read, Write};
5use std::vec::IntoIter;
6use std::path::{Path, PathBuf};
7
8/// 文件常规操作,如读写、目录创建、删除等
9pub struct FileUtil;
10
11impl FileUtil {
12    /// 罗列指定路径下的子目录及文件。
13    ///
14    /// 参数 `path` 是要罗列内容的目标路径。
15    /// 参数 `recurse` 决定是否以递归方式罗列子文件夹内的内容。
16    ///
17    /// 返回值是一个包含所有子目录及文件路径的 `Vec<String>`。
18    /// 如果在读取目录时发生错误,将返回一个空的 `Vec`。
19    pub fn list(path: &Path, recurse: bool) -> Vec<String> {
20        let mut result = Vec::new();
21        let entries = match fs::read_dir(path) {
22            Ok(entries) => entries,
23            Err(_) => return result,
24        };
25
26        for entry in entries {
27            let entry = match entry {
28                Ok(entry) => entry,
29                Err(_) => continue,
30            };
31            let path = entry.path();
32            if path.is_dir() {
33                if recurse {
34                    result.extend(Self::list(&path, true));
35                }
36                result.push(path.to_string_lossy().to_string());
37            } else {
38                result.push(path.to_string_lossy().to_string());
39            }
40        }
41        result
42    }
43
44    /// 获取指定文件的元数据信息
45    ///
46    /// 该函数封装了标准库中的 fs::metadata 方法,用于获取文件的基本信息,
47    /// 包括文件大小、创建时间、修改时间、文件类型等元数据。
48    ///
49    /// # 参数
50    /// * `file_path` - 要获取元数据的文件路径字符串引用
51    ///
52    /// # 返回值
53    /// 返回一个 io::Result<std::fs::Metadata> 类型:
54    /// * 成功时返回包含文件元数据的 Metadata 对象
55    /// * 失败时返回对应的 IO 错误信息
56    ///
57    /// # 错误处理
58    /// 当文件不存在或没有访问权限时,会返回相应的 IO 错误
59    pub fn metadata(file_path: &str) -> io::Result<std::fs::Metadata> {
60        fs::metadata(file_path)
61    }
62
63    /// 获取指定文件的最后修改时间
64    ///
65    /// # 参数
66    /// * `file_path` - 要查询的文件路径字符串引用
67    ///
68    /// # 返回值
69    /// 返回 Result<String, io::Error> 类型:
70    /// * 成功时返回格式化的时间字符串,格式为 "YYYY-MM-DD HH:MM:SS"
71    /// * 失败时返回对应的 IO 错误
72    ///
73    /// # 错误处理
74    /// 当文件不存在或无法访问时,会返回相应的 IO 错误
75    pub fn last_midified(file_path: &str) -> io::Result<String> {
76        // 获取文件元数据
77        let metadata = fs::metadata(file_path)?;
78        // 提取文件最后修改时间
79        let last_modified = metadata.modified().unwrap();
80        // 将系统时间转换为本地时间格式
81        let last_modified: DateTime<Local> = last_modified.into();
82        // 格式化时间为指定字符串格式并返回
83        Ok(last_modified.format("%Y-%m-%d %H:%M:%S").to_string())
84    }
85
86    /// 读取本地文件内容并以UTF-8字符串形式返回。
87    ///
88    /// 参数 `path` 是要读取的文件路径。
89    ///
90    /// 如果文件成功打开并读取,将返回包含文件内容的 `String`。
91    /// 如果在打开或读取文件时发生I/O错误,将返回对应的 `io::Error`。
92    pub fn read_string(path: &Path) -> Result<String, io::Error> {
93        let mut file = File::open(path)?;
94        let mut contents = String::new();
95        file.read_to_string(&mut contents)?;
96        Ok(contents)
97    }
98
99    pub fn read_string_by_iter(file_path: &str) -> Result<impl Iterator, io::Error> {
100        let file = File::open(file_path)?;
101        let reader = BufReader::new(file);
102        let xx = reader.lines();
103        Ok(xx)
104    }
105
106    /// 读取本地文件内容并以字节数组形式返回。
107    ///
108    /// 参数 `path` 是要读取的文件路径。
109    ///
110    /// 如果文件成功打开并读取,将返回包含文件内容的 `Vec<u8>`。
111    /// 如果在打开或读取文件时发生I/O错误,将返回对应的 `io::Error`。
112    pub fn read_bytes(path: &Path) -> Result<Vec<u8>, io::Error> {
113        let mut file = File::open(path)?;
114        let mut contents = Vec::new();
115        file.read_to_end(&mut contents)?;
116        Ok(contents)
117    }
118
119    /// 将给定的字符串内容以覆盖方式写入到指定路径的文本文件。
120    ///
121    /// 参数 `path` 是要写入的文件路径。
122    /// 参数 `content` 是要写入文件的字符串内容。
123    ///
124    /// 如果文件成功创建并写入内容,将返回 `Ok(())`。
125    /// 如果在创建或写入文件时发生I/O错误,将返回对应的 `io::Error`。
126    pub fn write_string(path: &Path, content: String) -> io::Result<()> {
127        let mut file = File::create(path)?;
128        file.write_all(content.as_bytes())?;
129        Ok(())
130    }
131
132    /// 将给定的字符串内容以追加方式写入到指定路径的文件。
133    ///
134    /// 参数 `path` 是要写入的文件路径。
135    /// 参数 `content` 是要追加到文件的字符串内容。
136    ///
137    /// 如果文件成功打开并追加内容,将返回 `Ok(())`。
138    /// 如果在打开或写入文件时发生I/O错误,将返回对应的 `io::Error`。
139    pub fn append_string(path: &Path, content: String) -> io::Result<()> {
140        let mut file = File::options().append(true).open(path)?;
141        file.write_all(content.as_bytes())?;
142        Ok(())
143    }
144
145    /// 将给定的字节数组内容写入到指定路径的文件。
146    ///
147    /// 参数 `path` 是要写入的文件路径。
148    /// 参数 `bytes` 是要写入文件的字节数组内容。
149    ///
150    /// 如果文件成功创建并写入内容,将返回 `Ok(())`。
151    /// 如果在创建或写入文件时发生I/O错误,将返回对应的 `io::Error`。
152    pub fn write_bytes(path: &Path, bytes: &[u8]) -> io::Result<()> {
153        let mut file = File::create(path)?;
154        file.write_all(bytes)?;
155        Ok(())
156    }
157
158    /// 创建目录及其所有必要的父目录
159    pub fn create_dir_with_parents(dir_path: &str) -> io::Result<()> {
160        fs::create_dir_all(dir_path)
161    }
162
163    /// 删除单个文件
164    pub fn delete_file(file_path: &str) -> io::Result<()> {
165        fs::remove_file(file_path)
166    }
167
168    /// 删除含有子文件的目录
169    pub fn delete_directory(dir_path: &str) -> io::Result<()> {
170        fs::remove_dir_all(dir_path)
171    }
172
173    /// 使用glob模式匹配文件路径
174    ///
175    /// 该函数接收一个glob模式字符串,搜索匹配的文件路径,并返回路径字符串的向量。
176    ///
177    /// # 参数
178    /// * `pattern` - glob模式字符串,用于匹配文件路径
179    ///
180    /// # 返回值
181    /// 返回一个Result类型,成功时包含匹配到的文件路径字符串向量,失败时包含IO错误
182    ///
183    /// # 示例
184    /// ```
185    /// let files = FileUtil::glob("D:/*/sub_dir").unwrap();
186    /// ```
187    pub fn glob(pattern: &str) -> io::Result<Vec<String>> {
188        let mut results = Vec::new();
189        let r = glob::glob(pattern);
190        if r.is_err() {
191            return Err(io::Error::new(io::ErrorKind::Other, r.err().unwrap().to_string()));
192        }
193        // 遍历glob模式匹配到的所有路径
194        for entry in r.unwrap() {
195            match entry {
196                Ok(path) => {
197                    let tmp = path.into_os_string().into_string().unwrap();
198                    let tmp = tmp.replace("\\", "/");
199                    results.push(tmp);
200                },
201                Err(e) => println!("{:?}", e),
202            }
203        }
204        Ok(results)
205    }
206
207    /// 递归遍历指定路径下的所有文件(不会把目录作为结果返回)
208    /// 
209    /// 该函数会递归遍历给定路径下的所有文件和子目录中的文件,
210    /// 并将所有文件路径收集到一个字符串向量中返回。
211    /// 
212    /// # 参数
213    /// * `path` - 需要遍历的根目录路径字符串
214    /// 
215    /// # 返回值
216    /// * `Ok(Vec<String>)` - 包含所有文件路径的字符串向量,路径分隔符统一为正斜杠
217    /// * `Err(Box<dyn std::error::Error>)` - 遍历过程中发生的错误
218    /// 
219    /// # 错误
220    /// 当路径不存在或无法访问时,会返回相应的错误信息
221    pub fn recursive_files(path: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
222        // 创建递归文件迭代器来遍历目录
223        let file_iterator = RecursiveFileIterator::new(Path::new(path))?;
224        
225        // 将所有文件路径转换为统一格式的字符串并收集到向量中
226        let results: Vec<String> = file_iterator.map(|item| {
227            let t = item.as_os_str().to_os_string().into_string().unwrap();
228            t.replace("\\", "/")
229        }).collect();
230        
231        Ok(results)
232    }
233}
234
235/// 递归文件迭代器结构体
236struct RecursiveFileIterator {
237    // 当前目录的条目迭代器
238    current_dir_iter: Option<IntoIter<DirEntry>>,
239    // 待处理的子目录队列
240    subdirs: Vec<PathBuf>,
241}
242
243impl RecursiveFileIterator {
244    pub fn new(root: &Path) -> Result<Self, std::io::Error> {
245        // 检查路径是否存在且是目录
246        if !root.exists() || !root.is_dir() {
247            return Err(std::io::Error::new(std::io::ErrorKind::NotFound, "目录不存在或不是目录"))
248        }
249
250        // 读取根目录并转换为迭代器
251        let entries: Vec<DirEntry> = std::fs::read_dir(root)?
252            .collect::<Result<Vec<DirEntry>, std::io::Error>>()?;
253
254        Ok(Self {
255            current_dir_iter: Some(entries.into_iter()),
256            subdirs: Vec::new(),
257        })  
258    }
259}
260
261/// 实现迭代器trait
262impl Iterator for RecursiveFileIterator {
263    type Item = PathBuf;
264
265    fn next(&mut self) -> Option<Self::Item> {
266        loop {
267            // 检查目录迭代器是否有内容
268            if let Some(iter) = &mut self.current_dir_iter {
269                if let Some(entry) = iter.next() {
270                    let path = entry.path();
271                    if path.is_dir() {
272                        self.subdirs.push(path);
273                    } else {
274                        return Some(path);
275                    }
276                } else {
277                    // 当前目录迭代器已耗尽,清楚它
278                    self.current_dir_iter = None;
279                }
280            } else {
281                // 当前目录迭代器已耗尽,尝试处理下一个子目录
282                if let Some(subdir) = self.subdirs.pop() {
283                    match std::fs::read_dir(&subdir) {
284                        Ok(entries) => {
285                            // 将目录条目转为迭代器
286                            let entries:Vec<DirEntry> = entries.filter_map(|e| e.ok()).collect();
287                            self.current_dir_iter = Some(entries.into_iter());
288                        },
289                        Err(e) => {
290                            eprintln!("无法读取目录:{:?}: {}", subdir, e);
291                        }
292                    }
293                } else {
294                    // 没有更多子目录需要处理,迭代结束
295                    return None;
296                }
297            }
298        }
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305    static ROOT_DIR: &str = "d:/tmp/";
306
307    #[test]
308    fn test_glob() {
309        let xx = FileUtil::glob("D:/*/sub_dir").unwrap();
310        println!("{:?}", xx);
311    }
312
313    #[test]
314    fn test_list() {
315        let temp_dir = Path::new(ROOT_DIR);
316        let sub_dir = temp_dir.join("sub_dir");
317        std::fs::create_dir(&sub_dir).expect("Failed to create sub dir");
318        let file_path = sub_dir.join("test_file.txt");
319        std::fs::File::create(&file_path).expect("Failed to create file");
320
321        let result = FileUtil::list(temp_dir, true);
322        assert!(result.contains(&file_path.to_string_lossy().to_string()));
323    }
324
325    #[test]
326    fn test_read_string() -> io::Result<()> {
327        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
328        println!("{:?}", temp_path);
329        let mut temp_file = File::options()
330            .write(true)
331            .create(true)
332            .open(temp_path.as_path())?;
333        let content = "test content".to_string();
334        temp_file
335            .write_all(content.as_bytes())
336            .expect("Failed to write to temp file");
337
338        let result = FileUtil::read_string(temp_path.as_path());
339        assert!(result.is_ok());
340        assert_eq!(result.unwrap(), content);
341        Ok(())
342    }
343
344    #[test]
345    fn test_read_bytes() {
346        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
347        let mut temp_file = File::options()
348            .write(true)
349            .open(temp_path.as_path())
350            .unwrap();
351        let content = [1, 2, 3];
352        temp_file
353            .write_all(&content)
354            .expect("Failed to write to temp file");
355
356        let result = FileUtil::read_bytes(temp_path.as_path());
357        assert!(result.is_ok());
358    }
359
360    #[test]
361    fn test_write_string() {
362        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
363        let _temp_file = File::options().write(true).open(&temp_path).unwrap();
364        let content = "test write content".to_string();
365
366        let result = FileUtil::write_string(&temp_path, content.clone());
367        assert!(result.is_ok());
368
369        let read_result = FileUtil::read_string(&temp_path);
370        assert!(read_result.is_ok());
371        assert_eq!(read_result.unwrap(), content);
372    }
373
374    #[test]
375    fn test_append_string() {
376        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
377        let _temp_file = File::open(&temp_path).unwrap();
378        let initial_content = "initial content".to_string();
379        let append_content = " appended content".to_string();
380        FileUtil::write_string(&temp_path, initial_content.clone())
381            .expect("Failed to write initial content");
382
383        let append_result = FileUtil::append_string(&temp_path, append_content.clone());
384        assert!(append_result.is_ok());
385
386        let read_result = FileUtil::read_string(&temp_path);
387        assert!(read_result.is_ok());
388        assert_eq!(read_result.unwrap(), initial_content + &append_content);
389    }
390
391    #[test]
392    fn test_write_bytes() {
393        let temp_path = Path::new(ROOT_DIR).join("temp_file.txt");
394        let _temp_file = File::open(&temp_path).unwrap();
395        let content = [4, 5, 6];
396
397        let result = FileUtil::write_bytes(&temp_path, &content);
398        assert!(result.is_ok());
399
400        let read_result = FileUtil::read_bytes(&temp_path);
401        assert!(read_result.is_ok());
402        assert_eq!(read_result.unwrap(), content);
403    }
404
405    #[test]
406    fn test_write_bytes_and_read_bytes_with_invalid_path() {
407
408        let results = FileUtil::recursive_files(r"D:\tmp\originalTifData\2025-02-24").unwrap();
409        for r in results {
410            println!("{}", r);
411        }
412    }
413}