use anyhow::{Context, Result};
use colored::*;
use std::io::{self, Write};
use std::process::Command;
#[cfg(unix)]
use users::get_effective_uid;
#[cfg(unix)]
pub fn check_root() -> bool {
get_effective_uid() == 0
}
#[cfg(windows)]
pub fn check_root() -> bool {
is_elevated::is_elevated()
}
#[cfg(not(any(unix, windows)))]
pub fn check_root() -> bool {
false
}
pub const fn supports_sudo_prompt() -> bool {
cfg!(unix)
}
#[cfg(unix)]
pub fn elevate_if_needed() -> Result<bool> {
if check_root() {
return Ok(true);
}
print_warning("System cleaners require root privileges.");
println!("You can either:");
println!(" 1. Run this command again with sudo");
println!(" 2. Enter your password to elevate now");
print!("\nWould you like to elevate now? [Y/n]: ");
io::stdout().flush()?;
let mut response = String::new();
io::stdin().read_line(&mut response)?;
match response.trim().to_lowercase().as_str() {
"n" | "no" => {
print_warning("Skipping system cleaners. Only user cleaners will run.");
Ok(false)
}
_ => {
print!("Authenticating... ");
io::stdout().flush()?;
let status = Command::new("sudo")
.args(["-v"])
.status()
.context("Failed to execute sudo")?;
if status.success() {
println!("{}", "✓ Authentication successful".green());
Ok(true)
} else {
print_error("Authentication failed. Skipping system cleaners.");
Ok(false)
}
}
}
}
#[cfg(windows)]
pub fn elevate_if_needed() -> Result<bool> {
if check_root() {
return Ok(true);
}
print_warning(
"Some system cleaners require Administrator privileges. Restart CleanSys as Administrator (right-click → 'Run as administrator') to use them.",
);
Ok(false)
}
#[cfg(not(any(unix, windows)))]
pub fn elevate_if_needed() -> Result<bool> {
print_warning("System cleaners are only available on Unix-like systems and Windows.");
Ok(false)
}
#[cfg(unix)]
pub fn execute_with_sudo(command: &str, args: &[&str]) -> Result<std::process::Output> {
use std::io::Write;
use std::process::Stdio;
if check_root() {
return Command::new(command)
.args(args)
.output()
.context(format!("Failed to execute command: {}", command));
}
if let Some(password) = crate::auth::cached_sudo_password() {
let mut child = Command::new("sudo")
.arg("-S")
.arg(command)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.context(format!("Failed to execute command with sudo: {}", command))?;
if let Some(mut stdin) = child.stdin.take() {
let _ = writeln!(stdin, "{}", password);
}
return child
.wait_with_output()
.context(format!("Failed to execute command with sudo: {}", command));
}
let mut sudo_args = vec!["-n", command];
sudo_args.extend_from_slice(args);
Command::new("sudo")
.args(sudo_args)
.stdin(Stdio::null())
.output()
.context(format!("Failed to execute command with sudo: {}", command))
}
#[cfg(not(unix))]
pub fn execute_with_sudo(command: &str, args: &[&str]) -> Result<std::process::Output> {
Command::new(command)
.args(args)
.output()
.context(format!("Failed to execute command: {}", command))
}
pub fn print_header(text: &str) {
let width = 60;
let padding = (width - text.len()) / 2;
let line = "=".repeat(width);
println!("\n{}", line.bright_blue());
println!(
"{}{}{}",
" ".repeat(padding),
text.bright_white().bold(),
" ".repeat(padding)
);
println!("{}\n", line.bright_blue());
}
pub fn print_success(message: &str) {
println!("{} {}", "✓".green().bold(), message);
}
pub fn print_warning(message: &str) {
println!("{} {}", "!".yellow().bold(), message);
}
pub fn print_error(message: &str) {
eprintln!("{} {}", "✗".red().bold(), message);
}
pub fn confirm(prompt: &str, default: bool) -> Result<bool> {
let yes_no = if default { "[Y/n]" } else { "[y/N]" };
loop {
print!("{} {} ", prompt, yes_no);
io::stdout().flush()?;
let mut response = String::new();
io::stdin().read_line(&mut response)?;
match response.trim().to_lowercase().as_str() {
"y" | "yes" => return Ok(true),
"n" | "no" => return Ok(false),
"" => return Ok(default),
_ => {
print_warning("Invalid response. Please enter 'y' or 'n'.");
}
}
}
}
pub fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} bytes", bytes)
}
}
pub fn get_size(path: &str) -> Result<u64> {
const MAX_DEPTH: u32 = 512;
Ok(dir_size(std::path::Path::new(path), 0, MAX_DEPTH))
}
fn dir_size(path: &std::path::Path, depth: u32, max_depth: u32) -> u64 {
let metadata = match std::fs::symlink_metadata(path) {
Ok(m) => m,
Err(_) => return 0,
};
if metadata.file_type().is_symlink() {
return 0;
}
if metadata.is_file() {
return metadata.len();
}
if metadata.is_dir() {
if depth >= max_depth {
log::warn!(
"get_size: max recursion depth ({max_depth}) reached at {:?}; size may be underestimated",
path
);
return 0;
}
let mut total = 0u64;
if let Ok(entries) = std::fs::read_dir(path) {
for entry in entries.flatten() {
total = total.saturating_add(dir_size(&entry.path(), depth + 1, max_depth));
}
}
return total;
}
0
}