Skip to main content

fast_clean/
lib.rs

1pub mod filter;
2pub mod remover;
3
4use crate::filter::{Filter, FilterEngine};
5use crate::remover::MockRemover;
6pub use crate::remover::RealRemover;
7use clap::Parser;
8use remover::Remover;
9use std::error::Error;
10use std::path::PathBuf;
11use walkdir::DirEntry;
12use walkdir::WalkDir;
13
14#[derive(Parser)]
15pub struct Cli {
16    /// 目标文件夹
17    #[arg(short = 'd')]
18    pub dir: String,
19    /// 过滤的文件夹
20    #[arg(long = "fd")]
21    pub filter_dir: Option<Vec<String>>,
22    /// 过滤的扩展名
23    #[arg(long = "ext")]
24    pub file_ext: Option<Vec<String>>,
25    /// 过滤的天数(超过此天数才被删除)
26    #[arg(long = "day")]
27    pub day: Option<i32>,
28    /// 过滤大小(超过此大小才被删除)
29    #[arg(long = "size")]
30    pub size: Option<String>,
31    /// 是否删除空文件夹
32    #[arg(long = "skip_dir")]
33    pub is_skip_dir: bool,
34    /// 是否是测试
35    #[arg(long = "is_test")]
36    pub is_test: bool,
37}
38
39/// 删除文件主逻辑
40pub fn run(args: &Cli) -> Result<Vec<PathBuf>, Box<dyn Error>> {
41    if args.is_test{
42        let mut mock_remover = MockRemover::new();
43        remove_files(args, &mut mock_remover)?;
44        Ok(mock_remover.removed.borrow().to_vec())
45    }else{
46        let mut real_remover = RealRemover {
47            is_skip_dir: args.is_skip_dir.clone(),
48        };
49        remove_files(args, &mut real_remover)?;
50        Ok(Vec::new())
51    }
52}
53
54pub fn remove_files<T:Remover>(args: &Cli, remover: &mut T) -> Result<(), Box<dyn Error>> {
55    let filter_engine = FilterEngine::from(&args)?;
56    let skip_dirs = args.filter_dir.clone().unwrap_or_default();
57    for entry in WalkDir::new(&args.dir)
58        .into_iter()
59        .filter_entry(|e| is_not_target_dir(e, &skip_dirs))
60        .filter_map(Result::ok)
61        .skip(1) // 第一个是dir,需要跳过
62    {
63        if let Ok(_) = filter_engine.filter(&entry) {
64            remover.remove(entry.path())?;
65        }
66    }
67    Ok(())
68}
69
70/// 判断是否跳过文件夹
71fn is_not_target_dir(entry: &DirEntry, skip_dirs: &Vec<String>) -> bool {
72    if skip_dirs.is_empty() {
73        return true;
74    }
75    if entry.file_type().is_dir() {
76        if let Some(name) = entry.file_name().to_str() {
77            return !skip_dirs.contains(&name.to_string());
78        }
79    }
80    true
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use std::fs::{create_dir, File};
87    use tempfile::tempdir;
88
89    /// 创建测试用 Cli
90    fn make_cli(dir: &str) -> Cli {
91        Cli {
92            dir: dir.to_string(),
93            filter_dir: None,
94            file_ext: None,
95            day: None,
96            size: None,
97            is_skip_dir: false,
98            is_test: true
99        }
100    }
101
102    #[test]
103    fn test_remove_all_files() {
104        let temp = tempdir().unwrap();
105        let file1 = temp.path().join("a.txt");
106        let file2 = temp.path().join("b.txt");
107
108        File::create(&file1).unwrap();
109        File::create(&file2).unwrap();
110
111        let cli = make_cli(temp.path().to_str().unwrap());
112
113        let ret = run(&cli).unwrap();
114        assert_eq!(ret.len(), 2);
115    }
116
117    #[test]
118    fn test_skip_specific_dir() {
119        let temp = tempdir().unwrap();
120
121        let skip_dir = temp.path().join("skip_me");
122        let normal_dir = temp.path().join("delete_me");
123
124        create_dir(&skip_dir).unwrap();
125        create_dir(&normal_dir).unwrap();
126
127        let file1 = skip_dir.join("a.txt");
128        let file2 = normal_dir.join("b.txt");
129
130        File::create(&file1).unwrap();
131        File::create(&file2).unwrap();
132
133        let cli = Cli {
134            dir: temp.path().to_str().unwrap().to_string(),
135            filter_dir: Some(vec!["skip_me".to_string()]),
136            file_ext: None,
137            day: None,
138            size: None,
139            is_skip_dir: false,
140            is_test: true
141        };
142
143        let ret = run(&cli).unwrap();
144        // 只应该删除 delete_me 里面的文件
145        assert_eq!(ret.len(), 2);
146        // 1 个文件 + 1 个目录 delete_me
147        // skip_me 整个目录树应该被 filter_entry 过滤掉
148    }
149}