Skip to main content

cleansys/utils/
mod.rs

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/// Check if the program is running with root privileges
9#[cfg(unix)]
10pub fn check_root() -> bool {
11    get_effective_uid() == 0
12}
13
14#[cfg(not(unix))]
15pub fn check_root() -> bool {
16    false
17}
18
19/// Prompt for sudo elevation if not already root
20/// Returns true if elevation succeeded or already root, false otherwise
21#[cfg(unix)]
22pub fn elevate_if_needed() -> Result<bool> {
23    if check_root() {
24        return Ok(true);
25    }
26
27    print_warning("System cleaners require root privileges.");
28    println!("You can either:");
29    println!("  1. Run this command again with sudo");
30    println!("  2. Enter your password to elevate now");
31    print!("\nWould you like to elevate now? [Y/n]: ");
32    io::stdout().flush()?;
33
34    let mut response = String::new();
35    io::stdin().read_line(&mut response)?;
36
37    match response.trim().to_lowercase().as_str() {
38        "n" | "no" => {
39            print_warning("Skipping system cleaners. Only user cleaners will run.");
40            Ok(false)
41        }
42        _ => {
43            // Try to validate sudo access by running a simple command
44            print!("Authenticating... ");
45            io::stdout().flush()?;
46
47            let status = Command::new("sudo")
48                .args(["-v"])
49                .status()
50                .context("Failed to execute sudo")?;
51
52            if status.success() {
53                println!("{}", "✓ Authentication successful".green());
54                Ok(true)
55            } else {
56                print_error("Authentication failed. Skipping system cleaners.");
57                Ok(false)
58            }
59        }
60    }
61}
62
63#[cfg(not(unix))]
64pub fn elevate_if_needed() -> Result<bool> {
65    print_warning("System cleaners are only available on Unix-like systems.");
66    Ok(false)
67}
68
69/// Execute a command with sudo if not already root
70/// This function handles terminal raw mode properly for TUI applications
71/// It assumes sudo credentials are already cached (via password dialog or sudo -v)
72#[cfg(unix)]
73pub fn execute_with_sudo(command: &str, args: &[&str]) -> Result<std::process::Output> {
74    use std::process::Stdio;
75
76    if check_root() {
77        // Already root, execute directly
78        Command::new(command)
79            .args(args)
80            .output()
81            .context(format!("Failed to execute command: {}", command))
82    } else {
83        // Use sudo with non-interactive mode and cached credentials
84        // The -n flag prevents sudo from prompting for a password
85        let mut sudo_args = vec!["-n", command];
86        sudo_args.extend_from_slice(args);
87
88        Command::new("sudo")
89            .args(sudo_args)
90            .stdin(Stdio::null())
91            .output()
92            .context(format!("Failed to execute command with sudo: {}", command))
93    }
94}
95
96#[cfg(not(unix))]
97pub fn execute_with_sudo(command: &str, args: &[&str]) -> Result<std::process::Output> {
98    Command::new(command)
99        .args(args)
100        .output()
101        .context(format!("Failed to execute command: {}", command))
102}
103
104/// Print a header with a colorful banner
105pub fn print_header(text: &str) {
106    let width = 60;
107    let padding = (width - text.len()) / 2;
108    let line = "=".repeat(width);
109
110    println!("\n{}", line.bright_blue());
111    println!(
112        "{}{}{}",
113        " ".repeat(padding),
114        text.bright_white().bold(),
115        " ".repeat(padding)
116    );
117    println!("{}\n", line.bright_blue());
118}
119
120/// Print a success message
121pub fn print_success(message: &str) {
122    println!("{} {}", "✓".green().bold(), message);
123}
124
125/// Print a warning message
126pub fn print_warning(message: &str) {
127    println!("{} {}", "!".yellow().bold(), message);
128}
129
130/// Print an error message
131pub fn print_error(message: &str) {
132    eprintln!("{} {}", "✗".red().bold(), message);
133}
134
135/// Ask for user confirmation
136pub fn confirm(prompt: &str, default: bool) -> Result<bool> {
137    let yes_no = if default { "[Y/n]" } else { "[y/N]" };
138    print!("{} {} ", prompt, yes_no);
139    io::stdout().flush()?;
140
141    let mut response = String::new();
142    io::stdin().read_line(&mut response)?;
143
144    match response.trim().to_lowercase().as_str() {
145        "y" | "yes" => Ok(true),
146        "n" | "no" => Ok(false),
147        "" => Ok(default),
148        _ => {
149            print_warning("Invalid response. Please enter 'y' or 'n'.");
150            confirm(prompt, default)
151        }
152    }
153}
154
155/// Format bytes into human-readable sizes
156pub fn format_size(bytes: u64) -> String {
157    const KB: u64 = 1024;
158    const MB: u64 = KB * 1024;
159    const GB: u64 = MB * 1024;
160
161    if bytes >= GB {
162        format!("{:.2} GB", bytes as f64 / GB as f64)
163    } else if bytes >= MB {
164        format!("{:.2} MB", bytes as f64 / MB as f64)
165    } else if bytes >= KB {
166        format!("{:.2} KB", bytes as f64 / KB as f64)
167    } else {
168        format!("{} bytes", bytes)
169    }
170}
171
172/// Get the size of a directory or file in bytes
173pub fn get_size(path: &str) -> Result<u64> {
174    let output = std::process::Command::new("du")
175        .args(["-sb", path])
176        .output()?;
177
178    if !output.status.success() {
179        return Ok(0);
180    }
181
182    let stdout = String::from_utf8_lossy(&output.stdout);
183    let parts: Vec<&str> = stdout.split_whitespace().collect();
184    if parts.is_empty() {
185        return Ok(0);
186    }
187
188    match parts[0].parse::<u64>() {
189        Ok(size) => Ok(size),
190        Err(_) => Ok(0),
191    }
192}