Skip to main content

diskforge_core/
scanner.rs

1use std::path::{Path, PathBuf};
2
3use anyhow::Result;
4use rayon::prelude::*;
5
6use crate::disk;
7use crate::rules::{android, dev_artifacts, docker, known_paths, simulator, user_rules};
8use crate::sizing;
9use crate::types::ScanResult;
10
11/// Options for controlling the scan
12pub struct ScanOptions {
13    /// Minimum item size to report (bytes)
14    pub min_size: u64,
15    /// Only report items older than this many seconds
16    pub older_than: Option<u64>,
17    /// Directories to scan for dev artifacts
18    pub project_dirs: Vec<PathBuf>,
19}
20
21impl Default for ScanOptions {
22    fn default() -> Self {
23        let home = std::env::var("HOME").unwrap_or_else(|_| "/Users".into());
24        Self {
25            min_size: 1024 * 1024, // 1 MB
26            older_than: None,
27            project_dirs: vec![PathBuf::from(&home)],
28        }
29    }
30}
31
32/// Run a full scan combining all rule categories
33pub fn scan(options: &ScanOptions) -> Result<ScanResult> {
34    let home = std::env::var("HOME")?;
35    let home_path = Path::new(&home);
36
37    // Get disk info
38    let disk_info = disk::get_disk_info(home_path)?;
39
40    let mut items = Vec::new();
41
42    // Phase 1: Known paths — parallel size computation via rayon
43    let rules = known_paths::all_known_path_rules(&home);
44    let known_items: Vec<_> = rules
45        .into_par_iter()
46        .filter(|rule| rule.path.exists())
47        .filter_map(|rule| {
48            let stats = sizing::dir_stats(&rule.path);
49            if stats.size >= options.min_size {
50                Some(rule.into_item_with_stats(stats.size, stats.last_modified))
51            } else {
52                None
53            }
54        })
55        .collect();
56    items.extend(known_items);
57
58    // Phase 2: Dev artifacts (filesystem walk)
59    for project_dir in &options.project_dirs {
60        if project_dir.exists() {
61            let dev_items = dev_artifacts::scan_dev_artifacts(project_dir);
62            items.extend(dev_items);
63        }
64    }
65
66    // Phase 3: iOS Simulator (devices + runtimes)
67    items.extend(simulator::scan_simulators(&home));
68
69    // Phase 4: Android SDK (NDK versions, platforms, build-tools, system-images, AVDs)
70    items.extend(android::scan_android(&home));
71
72    // Phase 5: Docker (images, containers, volumes, build cache via docker system df)
73    items.extend(docker::scan_docker(&home));
74
75    // Phase 6: User-defined rules (~/.config/diskforge/rules.toml)
76    items.extend(user_rules::scan_user_rules(&home));
77
78    // Filter by age if requested
79    if let Some(older_than_secs) = options.older_than {
80        let now = std::time::SystemTime::now();
81        items.retain(|item| {
82            item.last_modified
83                .and_then(|lm| now.duration_since(lm).ok())
84                .is_some_and(|age| age.as_secs() >= older_than_secs)
85        });
86    }
87
88    let mut result = ScanResult {
89        items,
90        disk_total: disk_info.total,
91        disk_used: disk_info.used,
92        disk_free: disk_info.free,
93    };
94
95    result.sort_by_size();
96    Ok(result)
97}