use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use crate::flake::template::{regenerate_flake, regenerate_flake_from_profile};
use crate::nixy_config::NixyConfig;
use crate::state::PackageState;
static COMPLETED: AtomicBool = AtomicBool::new(false);
static ROLLBACK_CONTEXT: Mutex<Option<RollbackContext>> = Mutex::new(None);
#[derive(Clone)]
pub enum OriginalState {
Legacy {
state_path: PathBuf,
state: PackageState,
},
NixyConfig {
nixy_json_path: PathBuf,
config: NixyConfig,
global_packages_dir: Option<PathBuf>,
},
}
#[derive(Clone)]
pub struct RollbackContext {
pub flake_dir: PathBuf,
pub original_state: OriginalState,
}
impl RollbackContext {
pub fn legacy(flake_dir: PathBuf, state_path: PathBuf, original_state: PackageState) -> Self {
Self {
flake_dir,
original_state: OriginalState::Legacy {
state_path,
state: original_state,
},
}
}
pub fn nixy_config(
flake_dir: PathBuf,
nixy_json_path: PathBuf,
config: NixyConfig,
global_packages_dir: Option<&Path>,
) -> Self {
Self {
flake_dir,
original_state: OriginalState::NixyConfig {
nixy_json_path,
config,
global_packages_dir: global_packages_dir.map(|p| p.to_path_buf()),
},
}
}
}
pub fn init_signal_handler() {
if let Err(e) = ctrlc::set_handler(move || {
if COMPLETED.load(Ordering::SeqCst) {
std::process::exit(130);
}
if let Some(ctx) = take_context() {
eprintln!("\nInterrupted. Rolling back changes...");
perform_rollback(&ctx);
}
std::process::exit(130); }) {
eprintln!(
"Warning: Failed to set Ctrl+C handler: {}. Rollback on interrupt will be unavailable.",
e
);
}
}
pub fn set_context(ctx: RollbackContext) {
COMPLETED.store(false, Ordering::SeqCst);
if let Ok(mut guard) = ROLLBACK_CONTEXT.lock() {
*guard = Some(ctx);
}
}
pub fn clear_context() {
if let Ok(mut guard) = ROLLBACK_CONTEXT.lock() {
*guard = None;
}
COMPLETED.store(true, Ordering::SeqCst);
}
fn take_context() -> Option<RollbackContext> {
ROLLBACK_CONTEXT
.lock()
.ok()
.and_then(|mut guard| guard.take())
}
fn perform_rollback(ctx: &RollbackContext) {
match &ctx.original_state {
OriginalState::Legacy { state_path, state } => {
if let Err(e) = state.save(state_path) {
eprintln!("Warning: Failed to restore packages.json: {}", e);
}
if let Err(e) = regenerate_flake(&ctx.flake_dir, state) {
eprintln!("Warning: Failed to restore flake.nix: {}", e);
}
}
OriginalState::NixyConfig {
nixy_json_path,
config,
global_packages_dir,
} => {
let content = match serde_json::to_string_pretty(config) {
Ok(c) => c,
Err(e) => {
eprintln!("Warning: Failed to serialize nixy.json for rollback: {}", e);
eprintln!("Rollback incomplete.");
return;
}
};
let tmp_path = nixy_json_path.with_extension("json.tmp");
if let Err(e) = std::fs::write(&tmp_path, &content) {
eprintln!("Warning: Failed to write nixy.json temp file: {}", e);
} else if let Err(e) = std::fs::rename(&tmp_path, nixy_json_path) {
eprintln!("Warning: Failed to restore nixy.json: {}", e);
let _ = std::fs::remove_file(&tmp_path);
}
if let Some(profile) = config.get_active_profile() {
let gpd = global_packages_dir.as_deref();
if let Err(e) = regenerate_flake_from_profile(&ctx.flake_dir, profile, gpd) {
eprintln!("Warning: Failed to restore flake.nix: {}", e);
}
}
}
}
eprintln!("Rollback complete.");
}