clean_dev_dirs/filtering.rs
1//! Project filtering functionality.
2//!
3//! This module provides functions for filtering projects based on various criteria
4//! such as size and modification time.
5
6use anyhow::Result;
7use chrono::{DateTime, Local};
8use rayon::prelude::*;
9use std::fs;
10
11use crate::config::FilterOptions;
12use crate::project::Project;
13use crate::utils::parse_size;
14
15/// Filter projects based on size and modification time criteria.
16///
17/// This function applies parallel filtering to remove projects that don't meet
18/// the specified criteria:
19/// - Projects smaller than the minimum size threshold
20/// - Projects modified more recently than the specified number of days
21///
22/// # Arguments
23///
24/// * `projects` - Vector of projects to filter
25/// * `filter_opts` - Filtering options containing size and time criteria
26///
27/// # Returns
28///
29/// - `Ok(Vec<Project>)` - Filtered list of projects that meet all criteria
30/// - `Err(anyhow::Error)` - If size parsing fails, or file system errors occur
31///
32/// # Errors
33///
34/// This function can return errors if:
35/// - The size string in `filter_opts.keep_size` cannot be parsed (invalid format)
36/// - Size value overflow occurs during parsing
37///
38/// # Examples
39///
40/// ```no_run
41/// # use clean_dev_dirs::{filtering::filter_projects, config::FilterOptions, project::Project};
42/// # use anyhow::Result;
43/// # fn example(projects: Vec<Project>) -> Result<()> {
44/// let filter_opts = FilterOptions {
45/// keep_size: "100MB".to_string(),
46/// keep_days: 30,
47/// };
48/// let filtered = filter_projects(projects, &filter_opts)?;
49/// # Ok(())
50/// # }
51/// ```
52pub fn filter_projects(
53 projects: Vec<Project>,
54 filter_opts: &FilterOptions,
55) -> Result<Vec<Project>> {
56 let keep_size_bytes = parse_size(&filter_opts.keep_size)?;
57 let keep_days = filter_opts.keep_days;
58
59 Ok(projects
60 .into_par_iter()
61 .filter(|project| meets_size_criteria(project, keep_size_bytes))
62 .filter(|project| meets_time_criteria(project, keep_days))
63 .collect())
64}
65
66/// Check if a project meets the size criteria.
67fn meets_size_criteria(project: &Project, min_size: u64) -> bool {
68 project.build_arts.size >= min_size
69}
70
71/// Check if a project meets the time criteria.
72fn meets_time_criteria(project: &Project, keep_days: u32) -> bool {
73 if keep_days == 0 {
74 return true;
75 }
76
77 is_project_old_enough(project, keep_days)
78}
79
80/// Check if a project is old enough based on its modification time.
81fn is_project_old_enough(project: &Project, keep_days: u32) -> bool {
82 let Result::Ok(metadata) = fs::metadata(&project.build_arts.path) else {
83 return true; // If we can't read metadata, don't filter it out
84 };
85
86 let Result::Ok(modified) = metadata.modified() else {
87 return true; // If we can't read modification time, don't filter it out
88 };
89
90 let modified_time: DateTime<Local> = modified.into();
91 let cutoff_time = Local::now() - chrono::Duration::days(i64::from(keep_days));
92
93 modified_time <= cutoff_time
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::project::{BuildArtifacts, Project, ProjectType};
100 use std::path::PathBuf;
101
102 /// Helper function to create a test project
103 fn create_test_project(
104 kind: ProjectType,
105 root_path: &str,
106 build_path: &str,
107 size: u64,
108 name: Option<String>,
109 ) -> Project {
110 Project::new(
111 kind,
112 PathBuf::from(root_path),
113 BuildArtifacts {
114 path: PathBuf::from(build_path),
115 size,
116 },
117 name,
118 )
119 }
120
121 #[test]
122 fn test_meets_size_criteria() {
123 let project = create_test_project(
124 ProjectType::Rust,
125 "/test",
126 "/test/target",
127 1_000_000, // 1MB
128 Some("test".to_string()),
129 );
130
131 assert!(meets_size_criteria(&project, 500_000)); // 0.5MB - should pass
132 assert!(meets_size_criteria(&project, 1_000_000)); // Exactly 1MB - should pass
133 assert!(!meets_size_criteria(&project, 2_000_000)); // 2MB - should fail
134 }
135
136 #[test]
137 fn test_meets_time_criteria_disabled() {
138 let project = create_test_project(
139 ProjectType::Rust,
140 "/test",
141 "/test/target",
142 1_000_000,
143 Some("test".to_string()),
144 );
145
146 // When keep_days is 0, should always return true
147 assert!(meets_time_criteria(&project, 0));
148 }
149}