1use anyhow::Result;
2use colored::*;
3use std::collections::HashMap;
4use std::io::{self, Write};
5
6use cleansys_core::utils::{print_success, print_warning};
7use cleansys_core::{
8 check_root, confirm, format_size, print_error, print_header, system_cleaners, user_cleaners,
9 CleanerFn,
10};
11
12pub struct MenuItem {
13 id: usize,
14 name: String,
15 description: String,
16 requires_root: bool,
17 function: CleanerFn,
18}
19
20pub struct Menu {
21 items: Vec<MenuItem>,
22 is_root: bool,
23}
24
25impl Default for Menu {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl Menu {
32 pub fn new() -> Self {
33 let is_root = check_root();
34 let mut items = Vec::new();
35 let mut id = 1;
36
37 for cleaner in user_cleaners::get_cleaners() {
39 items.push(MenuItem {
40 id,
41 name: cleaner.name.to_string(),
42 description: cleaner.description.to_string(),
43 requires_root: false,
44 function: cleaner.function,
45 });
46 id += 1;
47 }
48
49 for cleaner in system_cleaners::get_cleaners() {
51 items.push(MenuItem {
52 id,
53 name: cleaner.name.to_string(),
54 description: cleaner.description.to_string(),
55 requires_root: true,
56 function: cleaner.function,
57 });
58 id += 1;
59 }
60
61 Menu { items, is_root }
62 }
63
64 pub fn display(&self) -> Result<()> {
65 print_header("CLEAN MY SYSTEM");
66
67 println!("Select cleaning options (comma-separated numbers, e.g. 1,3,5):");
68 println!(
69 "0: [{}] Select all{}",
70 "ALL".green(),
71 if !self.is_root {
72 " (user cleaners only)"
73 } else {
74 ""
75 }
76 );
77
78 println!("\n{}", "USER CLEANERS:".blue().bold());
80 for item in &self.items {
81 if !item.requires_root {
82 println!("{}: [{}] {}", item.id, item.name.green(), item.description);
83 }
84 }
85
86 println!("\n{}", "SYSTEM CLEANERS:".red().bold());
87 for item in &self.items {
88 if item.requires_root {
89 let status = if self.is_root {
90 item.name.green()
91 } else {
92 format!("{} (requires root)", item.name).red()
93 };
94 println!("{}: [{}] {}", item.id, status, item.description);
95 }
96 }
97
98 Ok(())
99 }
100
101 pub fn run_interactive(&self) -> Result<()> {
102 self.display()?;
103
104 print!("\nEnter your choices (or 'q' to quit): ");
105 io::stdout().flush()?;
106
107 let mut input = String::new();
108 io::stdin().read_line(&mut input)?;
109
110 let input = input.trim();
111 if input.eq_ignore_ascii_case("q") {
112 return Ok(());
113 }
114
115 let selections = self.parse_selections(input);
116 self.run_selected_cleaners(selections)?;
117
118 Ok(())
119 }
120
121 fn parse_selections(&self, input: &str) -> Vec<usize> {
122 if input.trim() == "0" {
123 return self
125 .items
126 .iter()
127 .filter(|item| !item.requires_root || self.is_root)
128 .map(|item| item.id)
129 .collect();
130 }
131
132 input
133 .split(',')
134 .filter_map(|s| s.trim().parse::<usize>().ok())
135 .filter(|&id| id > 0 && id <= self.items.len())
136 .collect()
137 }
138
139 fn run_selected_cleaners(&self, selections: Vec<usize>) -> Result<()> {
140 if selections.is_empty() {
141 print_warning("No valid selections made. Exiting.");
142 return Ok(());
143 }
144
145 let mut total_saved: u64 = 0;
146 let mut skipped_items = Vec::new();
147
148 let id_map: HashMap<usize, &MenuItem> =
150 self.items.iter().map(|item| (item.id, item)).collect();
151
152 for id in selections {
153 if let Some(item) = id_map.get(&id) {
154 if item.requires_root && !self.is_root {
156 skipped_items.push(item.name.clone());
157 continue;
158 }
159
160 print_header(&format!("RUNNING: {}", item.name.to_uppercase()));
161
162 if confirm(&format!("Run '{}'?", item.name), true)? {
163 match (item.function)(cleansys_core::RunOptions::execute_with_confirmation()) {
164 Ok(result) => {
165 total_saved += result.total_bytes;
166 print_success(&format!(
167 "{} completed: freed {} across {} item(s)",
168 item.name,
169 format_size(result.total_bytes),
170 result.item_count()
171 ));
172 }
173 Err(err) => {
174 print_error(&format!("Error in {}: {}", item.name, err));
175 }
176 }
177 }
178 }
179 }
180
181 if !skipped_items.is_empty() {
182 print_warning(&format!(
183 "The following cleaners were skipped because they require root privileges: {}",
184 skipped_items.join(", ")
185 ));
186 }
187
188 print_header("CLEANING COMPLETE");
189 print_success(&format!("Total space freed: {}", format_size(total_saved)));
190
191 Ok(())
192 }
193}