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)]
99pub fn execute_with_sudo(command: &str, args: &[&str]) -> Result<std::process::Output> {
100 use std::process::Stdio;
101
102 if check_root() {
103 Command::new(command)
105 .args(args)
106 .output()
107 .context(format!("Failed to execute command: {}", command))
108 } else {
109 let mut sudo_args = vec!["-n", command];
112 sudo_args.extend_from_slice(args);
113
114 Command::new("sudo")
115 .args(sudo_args)
116 .stdin(Stdio::null())
117 .output()
118 .context(format!("Failed to execute command with sudo: {}", command))
119 }
120}
121
122#[cfg(not(unix))]
123pub fn execute_with_sudo(command: &str, args: &[&str]) -> Result<std::process::Output> {
124 Command::new(command)
125 .args(args)
126 .output()
127 .context(format!("Failed to execute command: {}", command))
128}
129
130pub fn print_header(text: &str) {
132 let width = 60;
133 let padding = (width - text.len()) / 2;
134 let line = "=".repeat(width);
135
136 println!("\n{}", line.bright_blue());
137 println!(
138 "{}{}{}",
139 " ".repeat(padding),
140 text.bright_white().bold(),
141 " ".repeat(padding)
142 );
143 println!("{}\n", line.bright_blue());
144}
145
146pub fn print_success(message: &str) {
148 println!("{} {}", "✓".green().bold(), message);
149}
150
151pub fn print_warning(message: &str) {
153 println!("{} {}", "!".yellow().bold(), message);
154}
155
156pub fn print_error(message: &str) {
158 eprintln!("{} {}", "✗".red().bold(), message);
159}
160
161pub fn confirm(prompt: &str, default: bool) -> Result<bool> {
163 let yes_no = if default { "[Y/n]" } else { "[y/N]" };
164 print!("{} {} ", prompt, yes_no);
165 io::stdout().flush()?;
166
167 let mut response = String::new();
168 io::stdin().read_line(&mut response)?;
169
170 match response.trim().to_lowercase().as_str() {
171 "y" | "yes" => Ok(true),
172 "n" | "no" => Ok(false),
173 "" => Ok(default),
174 _ => {
175 print_warning("Invalid response. Please enter 'y' or 'n'.");
176 confirm(prompt, default)
177 }
178 }
179}
180
181pub fn format_size(bytes: u64) -> String {
183 const KB: u64 = 1024;
184 const MB: u64 = KB * 1024;
185 const GB: u64 = MB * 1024;
186
187 if bytes >= GB {
188 format!("{:.2} GB", bytes as f64 / GB as f64)
189 } else if bytes >= MB {
190 format!("{:.2} MB", bytes as f64 / MB as f64)
191 } else if bytes >= KB {
192 format!("{:.2} KB", bytes as f64 / KB as f64)
193 } else {
194 format!("{} bytes", bytes)
195 }
196}
197
198pub fn get_size(path: &str) -> Result<u64> {
207 const MAX_DEPTH: u32 = 512;
208 Ok(dir_size(std::path::Path::new(path), 0, MAX_DEPTH))
209}
210
211fn dir_size(path: &std::path::Path, depth: u32, max_depth: u32) -> u64 {
212 let metadata = match std::fs::symlink_metadata(path) {
213 Ok(m) => m,
214 Err(_) => return 0,
215 };
216
217 if metadata.file_type().is_symlink() {
218 return 0;
219 }
220
221 if metadata.is_file() {
222 return metadata.len();
223 }
224
225 if metadata.is_dir() {
226 if depth >= max_depth {
231 log::warn!(
232 "get_size: max recursion depth ({max_depth}) reached at {:?}; size may be underestimated",
233 path
234 );
235 return 0;
236 }
237
238 let mut total = 0u64;
239 if let Ok(entries) = std::fs::read_dir(path) {
240 for entry in entries.flatten() {
241 total = total.saturating_add(dir_size(&entry.path(), depth + 1, max_depth));
242 }
243 }
244 return total;
245 }
246
247 0
248}