use microsandbox_utils::{XDG_BIN_DIR, XDG_HOME_DIR, XDG_LIB_DIR};
use std::path::{Path, PathBuf};
use tokio::fs;
use crate::MicrosandboxResult;
pub async fn clean() -> MicrosandboxResult<()> {
let bin_dir = XDG_HOME_DIR.join(XDG_BIN_DIR);
clean_user_scripts(&bin_dir).await?;
Ok(())
}
pub async fn uninstall() -> MicrosandboxResult<()> {
let bin_dir = XDG_HOME_DIR.join(XDG_BIN_DIR);
uninstall_executables(&bin_dir).await?;
uninstall_libraries().await?;
tracing::info!("microsandbox toolchain has been successfully uninstalled");
Ok(())
}
async fn uninstall_executables(bin_dir: &Path) -> MicrosandboxResult<()> {
let executables = ["msb", "msbrun", "msr", "msx", "msi", "msbserver"];
for executable in executables {
let executable_path = bin_dir.join(executable);
if executable_path.exists() {
fs::remove_file(&executable_path).await?;
tracing::info!("removed executable: {}", executable_path.display());
} else {
tracing::info!("executable not found: {}", executable_path.display());
}
}
Ok(())
}
async fn uninstall_libraries() -> MicrosandboxResult<()> {
let lib_dir = XDG_HOME_DIR.join(XDG_LIB_DIR);
remove_if_exists(lib_dir.join("libkrun.dylib")).await?;
remove_if_exists(lib_dir.join("libkrunfw.dylib")).await?;
remove_if_exists(lib_dir.join("libkrun.so")).await?;
remove_if_exists(lib_dir.join("libkrunfw.so")).await?;
uninstall_versioned_libraries(&lib_dir, "libkrun").await?;
uninstall_versioned_libraries(&lib_dir, "libkrunfw").await?;
Ok(())
}
async fn remove_if_exists(path: PathBuf) -> MicrosandboxResult<()> {
if path.exists() {
fs::remove_file(&path).await?;
tracing::info!("removed library: {}", path.display());
} else {
tracing::debug!("library not found: {}", path.display());
}
Ok(())
}
async fn uninstall_versioned_libraries(lib_dir: &Path, lib_prefix: &str) -> MicrosandboxResult<()> {
let mut entries = fs::read_dir(lib_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if let Some(filename) = path.file_name().and_then(|f| f.to_str()) {
let is_dylib =
filename.starts_with(&format!("{}.", lib_prefix)) && filename.ends_with(".dylib");
let is_so = filename.starts_with(&format!("{}.", lib_prefix))
|| filename.starts_with(&format!("{}.so.", lib_prefix));
if is_dylib || is_so {
fs::remove_file(&path).await?;
tracing::info!("removed versioned library: {}", path.display());
}
}
}
Ok(())
}
async fn clean_user_scripts(bin_dir: &Path) -> MicrosandboxResult<()> {
if !bin_dir.exists() {
tracing::info!("bin directory not found: {}", bin_dir.display());
return Ok(());
}
let protected_executables = ["msi", "msx", "msr"];
let mut entries = fs::read_dir(bin_dir).await?;
let mut removed_count = 0;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if !path.is_file() {
continue;
}
if let Some(filename) = path.file_name().and_then(|f| f.to_str()) {
if protected_executables.contains(&filename) {
tracing::debug!("skipping protected executable: {}", filename);
continue;
}
}
if let Ok(content) = fs::read_to_string(&path).await {
if content.contains("# MSB-ALIAS:") {
fs::remove_file(&path).await?;
tracing::info!("removed user script: {}", path.display());
removed_count += 1;
}
}
}
tracing::info!(
"removed {} user scripts with MSB-ALIAS markers",
removed_count
);
Ok(())
}