1use anyhow::{Context, Result};
2use colored::*;
3use std::io::{self, Write};
4use std::process::Command;
5#[cfg(unix)]
6use users::get_effective_uid;
7
8#[cfg(unix)]
11pub fn check_root() -> bool {
12 get_effective_uid() == 0
13}
14
15#[cfg(windows)]
17pub fn check_root() -> bool {
18 is_elevated::is_elevated()
19}
20
21#[cfg(not(any(unix, windows)))]
22pub fn check_root() -> bool {
23 false
24}
25
26pub const fn supports_sudo_prompt() -> bool {
31 cfg!(unix)
32}
33
34#[cfg(unix)]
37pub fn elevate_if_needed() -> Result<bool> {
38 if check_root() {
39 return Ok(true);
40 }
41
42 print_warning("System cleaners require root privileges.");
43 println!("You can either:");
44 println!(" 1. Run this command again with sudo");
45 println!(" 2. Enter your password to elevate now");
46 print!("\nWould you like to elevate now? [Y/n]: ");
47 io::stdout().flush()?;
48
49 let mut response = String::new();
50 io::stdin().read_line(&mut response)?;
51
52 match response.trim().to_lowercase().as_str() {
53 "n" | "no" => {
54 print_warning("Skipping system cleaners. Only user cleaners will run.");
55 Ok(false)
56 }
57 _ => {
58 print!("Authenticating... ");
60 io::stdout().flush()?;
61
62 let status = Command::new("sudo")
63 .args(["-v"])
64 .status()
65 .context("Failed to execute sudo")?;
66
67 if status.success() {
68 println!("{}", "✓ Authentication successful".green());
69 Ok(true)
70 } else {
71 print_error("Authentication failed. Skipping system cleaners.");
72 Ok(false)
73 }
74 }
75 }
76}
77
78#[cfg(windows)]
79pub fn elevate_if_needed() -> Result<bool> {
80 if check_root() {
81 return Ok(true);
82 }
83 print_warning(
84 "Some system cleaners require Administrator privileges. Restart CleanSys as Administrator (right-click → 'Run as administrator') to use them.",
85 );
86 Ok(false)
87}
88
89#[cfg(not(any(unix, windows)))]
90pub fn elevate_if_needed() -> Result<bool> {
91 print_warning("System cleaners are only available on Unix-like systems and Windows.");
92 Ok(false)
93}
94
95#[cfg(unix)]
110pub fn execute_with_sudo(command: &str, args: &[&str]) -> Result<std::process::Output> {
111 use std::io::Write;
112 use std::process::Stdio;
113
114 if check_root() {
115 return Command::new(command)
117 .args(args)
118 .output()
119 .context(format!("Failed to execute command: {}", command));
120 }
121
122 if let Some(password) = crate::auth::cached_sudo_password() {
123 let mut child = Command::new("sudo")
124 .arg("-S")
125 .arg(command)
126 .args(args)
127 .stdin(Stdio::piped())
128 .stdout(Stdio::piped())
129 .stderr(Stdio::piped())
130 .spawn()
131 .context(format!("Failed to execute command with sudo: {}", command))?;
132
133 if let Some(mut stdin) = child.stdin.take() {
134 let _ = writeln!(stdin, "{}", password);
135 }
136
137 return child
138 .wait_with_output()
139 .context(format!("Failed to execute command with sudo: {}", command));
140 }
141
142 let mut sudo_args = vec!["-n", command];
145 sudo_args.extend_from_slice(args);
146
147 Command::new("sudo")
148 .args(sudo_args)
149 .stdin(Stdio::null())
150 .output()
151 .context(format!("Failed to execute command with sudo: {}", command))
152}
153
154#[cfg(not(unix))]
155pub fn execute_with_sudo(command: &str, args: &[&str]) -> Result<std::process::Output> {
156 Command::new(command)
157 .args(args)
158 .output()
159 .context(format!("Failed to execute command: {}", command))
160}
161
162pub fn print_header(text: &str) {
164 let width = 60;
165 let padding = (width - text.len()) / 2;
166 let line = "=".repeat(width);
167
168 println!("\n{}", line.bright_blue());
169 println!(
170 "{}{}{}",
171 " ".repeat(padding),
172 text.bright_white().bold(),
173 " ".repeat(padding)
174 );
175 println!("{}\n", line.bright_blue());
176}
177
178pub fn print_success(message: &str) {
180 println!("{} {}", "✓".green().bold(), message);
181}
182
183pub fn print_warning(message: &str) {
185 println!("{} {}", "!".yellow().bold(), message);
186}
187
188pub fn print_error(message: &str) {
190 eprintln!("{} {}", "✗".red().bold(), message);
191}
192
193pub fn confirm(prompt: &str, default: bool) -> Result<bool> {
195 let yes_no = if default { "[Y/n]" } else { "[y/N]" };
196 loop {
197 print!("{} {} ", prompt, yes_no);
198 io::stdout().flush()?;
199
200 let mut response = String::new();
201 io::stdin().read_line(&mut response)?;
202
203 match response.trim().to_lowercase().as_str() {
204 "y" | "yes" => return Ok(true),
205 "n" | "no" => return Ok(false),
206 "" => return Ok(default),
207 _ => {
208 print_warning("Invalid response. Please enter 'y' or 'n'.");
209 }
213 }
214 }
215}
216
217pub fn format_size(bytes: u64) -> String {
219 const KB: u64 = 1024;
220 const MB: u64 = KB * 1024;
221 const GB: u64 = MB * 1024;
222
223 if bytes >= GB {
224 format!("{:.2} GB", bytes as f64 / GB as f64)
225 } else if bytes >= MB {
226 format!("{:.2} MB", bytes as f64 / MB as f64)
227 } else if bytes >= KB {
228 format!("{:.2} KB", bytes as f64 / KB as f64)
229 } else {
230 format!("{} bytes", bytes)
231 }
232}
233
234pub fn get_size(path: &str) -> Result<u64> {
245 const MAX_DEPTH: u32 = 512;
246 Ok(dir_size(std::path::Path::new(path), 0, MAX_DEPTH))
247}
248
249fn dir_size(path: &std::path::Path, depth: u32, max_depth: u32) -> u64 {
250 let metadata = match std::fs::symlink_metadata(path) {
251 Ok(m) => m,
252 Err(_) => return 0,
253 };
254
255 if metadata.file_type().is_symlink() {
256 return 0;
257 }
258
259 if metadata.is_file() {
260 return metadata.len();
261 }
262
263 if metadata.is_dir() {
264 if depth >= max_depth {
269 log::warn!(
270 "get_size: max recursion depth ({max_depth}) reached at {:?}; size may be underestimated",
271 path
272 );
273 return 0;
274 }
275
276 let mut total = 0u64;
277 if let Ok(entries) = std::fs::read_dir(path) {
278 for entry in entries.flatten() {
279 total = total.saturating_add(dir_size(&entry.path(), depth + 1, max_depth));
280 }
281 }
282 return total;
283 }
284
285 0
286}