use crate::config::{get_biovault_home, Config};
use crate::Result;
use anyhow::Context;
use colored::*;
use dialoguer::{theme::ColorfulTheme, Confirm};
use std::fs;
use std::path::PathBuf;
use tracing::{error, info, warn};
#[derive(Debug)]
pub struct CleanupPath {
pub description: String,
pub path: PathBuf,
pub exists: bool,
}
fn get_cleanup_paths(config: &Config) -> Vec<CleanupPath> {
let mut paths = Vec::new();
let biovault_home = get_biovault_home().unwrap_or_else(|_| PathBuf::from("~/.biovault"));
paths.push(CleanupPath {
description: "BioVault home directory".to_string(),
path: biovault_home.clone(),
exists: biovault_home.exists(),
});
let data_dir = if let Ok(data_dir) = config.get_syftbox_data_dir() {
Some(data_dir)
} else if let Ok(data_dir) = std::env::var("SYFTBOX_DATA_DIR") {
Some(PathBuf::from(data_dir))
} else {
None
};
if let Some(data_dir) = data_dir {
let (real_data_dir, datasite_path) =
if data_dir.components().any(|c| c.as_os_str() == "datasites")
&& data_dir.to_string_lossy().contains(&config.email)
{
let mut parent = data_dir.clone();
while parent.components().any(|c| c.as_os_str() == "datasites") {
if let Some(p) = parent.parent() {
parent = p.to_path_buf();
} else {
break;
}
}
(parent, data_dir.clone())
} else {
let datasite = data_dir.join("datasites").join(&config.email);
(data_dir.clone(), datasite)
};
info!("Using datasite path: {}", datasite_path.display());
let public_biovault = datasite_path.join("public").join("biovault");
paths.push(CleanupPath {
description: "└─ public/biovault".to_string(),
path: public_biovault.clone(),
exists: public_biovault.exists(),
});
let shared_submissions = datasite_path
.join("shared")
.join("biovault")
.join("submissions");
paths.push(CleanupPath {
description: "└─ shared/biovault/submissions".to_string(),
path: shared_submissions.clone(),
exists: shared_submissions.exists(),
});
let app_data_biovault = datasite_path.join("app_data").join("biovault");
paths.push(CleanupPath {
description: "└─ app_data/biovault (includes RPC)".to_string(),
path: app_data_biovault.clone(),
exists: app_data_biovault.exists(),
});
let private_biovault = real_data_dir
.join("private")
.join("app_data")
.join("biovault");
paths.push(CleanupPath {
description: "Private app_data/biovault".to_string(),
path: private_biovault.clone(),
exists: private_biovault.exists(),
});
} else {
warn!("Could not determine SyftBox data directory - some paths may not be cleaned");
}
paths
}
fn delete_path(path: &PathBuf) -> anyhow::Result<()> {
if path.exists() {
if path.is_dir() {
fs::remove_dir_all(path)
.with_context(|| format!("Failed to remove directory: {}", path.display()))?;
} else {
fs::remove_file(path)
.with_context(|| format!("Failed to remove file: {}", path.display()))?;
}
}
Ok(())
}
pub async fn execute(ignore_warning: bool) -> Result<()> {
println!("\n{}", "⚠️ BioVault Hard Reset".red().bold());
println!(
"{}",
"This will DELETE all BioVault data and configuration!".red()
);
println!();
let config = match Config::get_config_path().and_then(Config::from_file) {
Ok(c) => {
info!("Loaded existing config for email: {}", c.email);
c
}
Err(e) => {
warn!(
"No existing config found ({}), using minimal config for cleanup",
e
);
Config {
email: std::env::var("SYFTBOX_EMAIL")
.unwrap_or_else(|_| "unknown@email.com".to_string()),
syftbox_config: None,
version: None,
binary_paths: None,
}
}
};
let paths = get_cleanup_paths(&config);
println!("{}", "The following paths will be deleted:".yellow().bold());
println!();
let mut any_exists = false;
for (i, path_info) in paths.iter().enumerate() {
if i == 1 && paths.len() > 1 {
if let Some(first_datasite_path) =
paths.iter().find(|p| p.description.starts_with("└─"))
{
if let Some(parent) = first_datasite_path.path.parent() {
if let Some(parent2) = parent.parent() {
println!("\nDatasite: {}", parent2.display().to_string().cyan());
}
}
}
}
if path_info.description == "Private app_data/biovault" {
if let Some(parent) = path_info.path.parent() {
if let Some(parent2) = parent.parent() {
if let Some(parent3) = parent2.parent() {
println!(
"\nSyftBox Data Dir: {}",
parent3.display().to_string().cyan()
);
}
}
}
}
if path_info.exists {
any_exists = true;
if path_info.description.starts_with("└─") {
println!(" {} {}", "✓".green(), path_info.description);
} else if path_info.description == "Private app_data/biovault" {
println!(" {} └─ private/app_data/biovault", "✓".green());
} else {
println!(
" {} {} ({})",
"✓".green(),
path_info.description,
path_info.path.display().to_string().dimmed()
);
}
} else if path_info.description.starts_with("└─") {
println!(
" {} {} {}",
"○".dimmed(),
path_info.description.dimmed(),
"(does not exist)".dimmed()
);
} else if path_info.description == "Private app_data/biovault" {
println!(
" {} {} {}",
"○".dimmed(),
"└─ private/app_data/biovault".dimmed(),
"(does not exist)".dimmed()
);
} else {
println!(
" {} {} ({}) {}",
"○".dimmed(),
path_info.description.dimmed(),
path_info.path.display().to_string().dimmed(),
"(does not exist)".dimmed()
);
}
}
if !any_exists {
println!();
println!("{}", "No BioVault data found to delete.".green());
return Ok(());
}
println!();
if !ignore_warning {
let theme = ColorfulTheme::default();
let confirmed = Confirm::with_theme(&theme)
.with_prompt("Are you sure you want to delete all BioVault data?")
.default(false)
.interact()
.map_err(|e| anyhow::anyhow!("Failed to get confirmation: {}", e))?;
if !confirmed {
println!("{}", "Operation cancelled.".yellow());
return Ok(());
}
let really_confirmed = Confirm::with_theme(&theme)
.with_prompt("This action CANNOT be undone. Continue?")
.default(false)
.interact()
.map_err(|e| anyhow::anyhow!("Failed to get confirmation: {}", e))?;
if !really_confirmed {
println!("{}", "Operation cancelled.".yellow());
return Ok(());
}
} else {
info!("Running in non-interactive mode (--ignore-warning)");
}
println!();
println!("{}", "Deleting BioVault data...".yellow());
let mut errors = Vec::new();
let mut successes = 0;
for path_info in paths {
if path_info.exists {
print!(" Deleting {}... ", path_info.description);
match delete_path(&path_info.path) {
Ok(_) => {
println!("{}", "✓".green());
successes += 1;
}
Err(e) => {
println!("{}", "✗".red());
error!("Failed to delete {}: {}", path_info.path.display(), e);
errors.push((path_info.description.clone(), e));
}
}
}
}
println!();
if !errors.is_empty() {
println!("{}", "⚠️ Some paths could not be deleted:".yellow().bold());
for (desc, err) in errors {
println!(" {} {}: {}", "✗".red(), desc, err.to_string().red());
}
println!();
println!(
"{}",
"You may need to manually delete these paths with appropriate permissions.".yellow()
);
}
if successes > 0 {
println!(
"{} {} paths deleted successfully",
"✓".green().bold(),
successes
);
println!();
println!("{}", "BioVault has been reset.".green().bold());
println!("Run {} to set up a fresh installation.", "`bv init`".cyan());
}
Ok(())
}