Skip to main content

cleansys_tui/
menu.rs

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        // Add user cleaner items
38        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        // Add system cleaner items
50        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        // Group items by user/system
79        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 all ids that don't require root or all ids if we are root
124            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        // Create a map of id to items for easier lookup
149        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                // Skip system cleaners if not root
155                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}