use anyhow::{Context, Result};
use std::path::PathBuf;
use std::io::{self, Write};
use std::process::{Command, Stdio};
use std::env;
use std::thread;
use std::time::Duration;
use log::{info, debug};
use crate::ui::{output_format, theme};
pub fn run_uninstall_command(
yes_flag: bool,
theme_map: &std::collections::HashMap<theme::ThemeEntry, theme::ThemeStyle>,
) -> Result<()> {
info!("Starting cleansh uninstall operation.");
debug!("[uninstall.rs] Uninstall command initiated.");
if !yes_flag {
output_format::print_info_message(
&mut io::stderr(),
"WARNING: This will uninstall Cleansh and remove its associated data.",
theme_map,
)?;
output_format::print_message(
&mut io::stderr(),
"Are you sure you want to proceed? (y/N): ",
theme_map,
Some(theme::ThemeEntry::Prompt),
)?;
io::stderr().flush()?;
let mut confirmation = String::new();
io::stdin().read_line(&mut confirmation)
.context("Failed to read confirmation input.")?;
if confirmation.trim().to_lowercase() != "y" {
output_format::print_info_message(
&mut io::stderr(),
"Uninstallation cancelled.",
theme_map,
)?;
return Ok(());
}
}
let current_exe_path = env::current_exe()
.context("Failed to determine current executable path.")?;
debug!("[uninstall.rs] Current executable path: {:?}", current_exe_path);
let app_state_file_path = std::env::var("CLEANSH_STATE_FILE_OVERRIDE_FOR_TESTS")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let mut path = dirs::config_dir().unwrap_or_else(|| PathBuf::from("."));
path.push("cleansh"); path.push("app_state.json");
path
});
debug!("[uninstall.rs] App state file path: {:?}", app_state_file_path);
let app_state_dir = app_state_file_path.parent()
.map(PathBuf::from)
.unwrap_or_else(|| {
debug!("[uninstall.rs] Could not determine parent directory for app state file. Defaulting to current directory.");
PathBuf::from(".")
});
debug!("[uninstall.rs] App state directory: {:?}", app_state_dir);
output_format::print_info_message(
&mut io::stderr(),
"Initiating self-deletion process...",
theme_map,
)?;
#[cfg(target_os = "windows")]
{
let powershell_script = format!(
r#"
Start-Sleep -Seconds 1
$exePath = "{}"
$appStateFile = "{}"
$appStateDir = "{}"
Write-Host "Attempting to delete executable: $exePath"
try {{
Remove-Item -Path $exePath -Force -ErrorAction Stop
Write-Host "Executable deleted successfully."
}} catch {{
Write-Error "Failed to delete executable: $($_.Exception.Message)"
exit 1
}}
Write-Host "Attempting to delete app state file: $appStateFile"
try {{
if (Test-Path $appStateFile) {{
Remove-Item -Path $appStateFile -Force -ErrorAction Stop
Write-Host "App state file deleted successfully."
}} else {{
Write-Host "App state file not found, skipping deletion."
}}
}} catch {{
Write-Error "Failed to delete app state file: $($_.Exception.Message)"
exit 1
}}
Write-Host "Attempting to delete app state directory: $appStateDir"
try {{
# Only remove directory if it's empty or contains only app_state.json (which is now deleted)
# This is safer than -Recurse if other user files might be there, but we assume cleansh only puts app_state.json here.
# For a more aggressive cleanup, -Recurse could be used, but it's risky.
if (Test-Path $appStateDir) {{
# Check if directory is empty or only contains the app_state.json (which should be gone)
# This is a heuristic, a robust check would be more complex.
# For now, let's just try to remove it if it exists and is empty or contains only expected files.
Remove-Item -Path $appStateDir -Recurse -Force -ErrorAction Stop
Write-Host "App state directory deleted successfully."
}} else {{
Write-Host "App state directory not found, skipping deletion."
}}
}} catch {{
Write-Error "Failed to delete app state directory: $($_.Exception.Message)"
exit 1
}}
Write-Host "Cleansh uninstallation complete."
exit 0
"#,
current_exe_path.to_string_lossy().replace("'", "''"), app_state_file_path.to_string_lossy().replace("'", "''"),
app_state_dir.to_string_lossy().replace("'", "''")
);
debug!("[uninstall.rs] PowerShell script to execute:\n{}", powershell_script);
let mut command = Command::new("powershell.exe");
command.arg("-NoProfile")
.arg("-NonInteractive")
.arg("-Command")
.arg(&powershell_script)
.stdin(Stdio::null())
.stdout(Stdio::null()) .stderr(Stdio::null());
let child = command.spawn()
.context("Failed to spawn PowerShell process for uninstallation.")?;
debug!("[uninstall.rs] PowerShell helper spawned with PID: {}", child.id());
}
#[cfg(not(target_os = "windows"))]
{
let bash_script = format!(
r#"
#!/bin/bash
sleep 1
exe_path="{}"
app_state_file="{}"
app_state_dir="{}"
echo "Attempting to delete executable: $exe_path"
rm -f "$exe_path"
if [ $? -ne 0 ]; then
echo "Error: Failed to delete executable: $exe_path" >&2
exit 1
fi
echo "Executable deleted successfully."
echo "Attempting to delete app state file: $app_state_file"
if [ -f "$app_state_file" ]; then
rm -f "$app_state_file"
if [ $? -ne 0 ]; then
echo "Error: Failed to delete app state file: $app_state_file" >&2
exit 1
fi
echo "App state file deleted successfully."
else
echo "App state file not found, skipping deletion."
fi
echo "Attempting to delete app state directory: $app_state_dir"
if [ -d "$app_state_dir" ]; then
# rmdir only deletes empty directories. rm -rf is more aggressive but also more dangerous.
# Given cleansh only creates app_state.json here, rmdir should be fine after file deletion.
rmdir "$app_state_dir" 2>/dev/null || true # Suppress error if not empty, but try to remove
if [ $? -ne 0 ]; then
echo "Warning: App state directory '$app_state_dir' might not be empty or could not be removed." >&2
else
echo "App state directory deleted successfully."
fi
else
echo "App state directory not found, skipping deletion."
fi
echo "Cleansh uninstallation complete."
exit 0
"#,
current_exe_path.to_string_lossy(),
app_state_file_path.to_string_lossy(),
app_state_dir.to_string_lossy()
);
debug!("[uninstall.rs] Bash script to execute:\n{}", bash_script);
let mut command = Command::new("bash");
command.arg("-c")
.arg(&bash_script)
.stdin(Stdio::null())
.stdout(Stdio::null()) .stderr(Stdio::null());
let child = command.spawn()
.context("Failed to spawn bash process for uninstallation.")?;
debug!("[uninstall.rs] Bash helper spawned with PID: {}", child.id());
}
thread::sleep(Duration::from_millis(100));
output_format::print_info_message(
&mut io::stderr(),
"Cleansh is being uninstalled. You can close this terminal.",
theme_map,
)?;
std::process::exit(0);
}