Skip to main content

null_e/cleaners/
mod.rs

1//! Specialized cleanup modules for different development environments
2//!
3//! This module contains cleanup handlers for:
4//! - Xcode (iOS/macOS development)
5//! - Android Studio
6//! - Docker
7//! - ML/AI tools (Huggingface, Ollama, PyTorch)
8//! - IDE caches (JetBrains, VS Code)
9//! - System logs
10//! - Homebrew
11//! - iOS Dependencies (CocoaPods, Carthage, SPM)
12//! - Electron apps
13//! - Game Development (Unity, Unreal, Godot)
14//! - Cloud CLI (AWS, GCP, Azure, kubectl)
15//! - macOS System (orphaned containers, caches)
16//! - Misc tools (Vagrant, Git LFS, Go, Ruby, NuGet, Gradle, Maven)
17//! - Testing browsers (Playwright, Cypress, Puppeteer, Selenium)
18//! - System cleanup (Trash, Downloads, Temp, Big Files)
19//! - Language Runtimes (nvm, pyenv, rbenv, rustup, sdkman, gvm)
20
21pub mod xcode;
22pub mod android;
23pub mod docker;
24pub mod ml;
25pub mod ide;
26pub mod logs;
27pub mod homebrew;
28pub mod ios_deps;
29pub mod electron;
30pub mod gamedev;
31pub mod cloud;
32pub mod macos;
33pub mod misc;
34pub mod browsers_test;
35pub mod system;
36pub mod runtimes;
37pub mod binaries;
38
39use crate::error::Result;
40use serde::{Deserialize, Serialize};
41use std::path::PathBuf;
42use std::time::SystemTime;
43
44/// A cleanable item found by a cleaner module
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct CleanableItem {
47    /// Human-readable name
48    pub name: String,
49    /// Category (e.g., "Xcode", "Docker")
50    pub category: String,
51    /// Subcategory (e.g., "DerivedData", "Simulators")
52    pub subcategory: String,
53    /// Icon for display
54    pub icon: &'static str,
55    /// Full path
56    pub path: PathBuf,
57    /// Size in bytes
58    pub size: u64,
59    /// Number of files (if applicable)
60    pub file_count: Option<u64>,
61    /// Last modification time
62    pub last_modified: Option<SystemTime>,
63    /// Description of what this is
64    pub description: &'static str,
65    /// Is it safe to delete?
66    pub safe_to_delete: SafetyLevel,
67    /// Official clean command (if available)
68    pub clean_command: Option<String>,
69}
70
71/// Safety level for deletion
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
73pub enum SafetyLevel {
74    /// Safe to delete, will be regenerated
75    Safe,
76    /// Safe but may slow down next build/operation
77    SafeWithCost,
78    /// Use caution - may lose some data
79    Caution,
80    /// Dangerous - may break things
81    Dangerous,
82}
83
84impl SafetyLevel {
85    /// Get a color hint for display
86    pub fn color_hint(&self) -> &'static str {
87        match self {
88            Self::Safe => "green",
89            Self::SafeWithCost => "yellow",
90            Self::Caution => "red",
91            Self::Dangerous => "magenta",
92        }
93    }
94
95    /// Get a symbol for display
96    pub fn symbol(&self) -> &'static str {
97        match self {
98            Self::Safe => "✓",
99            Self::SafeWithCost => "~",
100            Self::Caution => "!",
101            Self::Dangerous => "⚠",
102        }
103    }
104}
105
106impl CleanableItem {
107    /// Check if this item exists
108    pub fn exists(&self) -> bool {
109        self.path.exists()
110    }
111
112    /// Get age in days
113    pub fn age_days(&self) -> Option<u64> {
114        self.last_modified
115            .and_then(|t| t.elapsed().ok())
116            .map(|d| d.as_secs() / 86400)
117    }
118
119    /// Format the last used time
120    pub fn last_used_display(&self) -> String {
121        match self.age_days() {
122            Some(0) => "today".to_string(),
123            Some(1) => "yesterday".to_string(),
124            Some(d) if d < 7 => format!("{} days ago", d),
125            Some(d) if d < 30 => format!("{} weeks ago", d / 7),
126            Some(d) if d < 365 => format!("{} months ago", d / 30),
127            Some(d) => format!("{} years ago", d / 365),
128            None => "unknown".to_string(),
129        }
130    }
131}
132
133/// Summary of cleanable items from all modules
134#[derive(Debug, Default)]
135pub struct CleanerSummary {
136    pub total_items: usize,
137    pub total_size: u64,
138    pub by_category: std::collections::HashMap<String, CategorySummary>,
139}
140
141/// Summary for a single category
142#[derive(Debug, Default, Clone)]
143pub struct CategorySummary {
144    pub name: String,
145    pub icon: &'static str,
146    pub item_count: usize,
147    pub total_size: u64,
148}
149
150impl CleanerSummary {
151    pub fn from_items(items: &[CleanableItem]) -> Self {
152        let mut summary = Self::default();
153        summary.total_items = items.len();
154        summary.total_size = items.iter().map(|i| i.size).sum();
155
156        for item in items {
157            let entry = summary.by_category
158                .entry(item.category.clone())
159                .or_insert_with(|| CategorySummary {
160                    name: item.category.clone(),
161                    icon: item.icon,
162                    ..Default::default()
163                });
164            entry.item_count += 1;
165            entry.total_size += item.size;
166        }
167
168        summary
169    }
170}
171
172/// Calculate directory size recursively
173pub fn calculate_dir_size(path: &std::path::Path) -> Result<(u64, u64)> {
174    use rayon::prelude::*;
175    use walkdir::WalkDir;
176
177    if !path.exists() {
178        return Ok((0, 0));
179    }
180
181    let entries: Vec<_> = WalkDir::new(path)
182        .into_iter()
183        .filter_map(|e| e.ok())
184        .collect();
185
186    let (size, count): (u64, u64) = entries
187        .par_iter()
188        .filter_map(|entry| entry.metadata().ok())
189        .filter(|m| m.is_file())
190        .fold(
191            || (0u64, 0u64),
192            |(size, count), m| (size + m.len(), count + 1),
193        )
194        .reduce(|| (0, 0), |(s1, c1), (s2, c2)| (s1 + s2, c1 + c2));
195
196    Ok((size, count))
197}
198
199/// Get last modification time of a path
200pub fn get_mtime(path: &std::path::Path) -> Option<SystemTime> {
201    std::fs::metadata(path).ok()?.modified().ok()
202}