use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
static INTERRUPTED: AtomicBool = AtomicBool::new(false);
pub fn is_interrupted() -> bool {
INTERRUPTED.load(Ordering::Relaxed)
}
pub fn reset_interrupted() {
INTERRUPTED.store(false, Ordering::SeqCst);
}
#[derive(Clone)]
pub struct InterruptState {
interrupted: Arc<AtomicBool>,
completed: Arc<AtomicUsize>,
total: Arc<AtomicUsize>,
}
impl InterruptState {
pub fn new() -> Self {
Self {
interrupted: Arc::new(AtomicBool::new(false)),
completed: Arc::new(AtomicUsize::new(0)),
total: Arc::new(AtomicUsize::new(0)),
}
}
pub fn set_total(&self, total: usize) {
self.total.store(total, Ordering::SeqCst);
}
pub fn increment_completed(&self) {
self.completed.fetch_add(1, Ordering::SeqCst);
}
pub fn mark_interrupted(&self) {
self.interrupted.store(true, Ordering::SeqCst);
}
pub fn was_interrupted(&self) -> bool {
self.interrupted.load(Ordering::Relaxed) || is_interrupted()
}
pub fn files_completed(&self) -> usize {
self.completed.load(Ordering::Relaxed)
}
pub fn total_files(&self) -> usize {
self.total.load(Ordering::Relaxed)
}
pub fn check_interrupt(&self) -> bool {
if is_interrupted() {
self.mark_interrupted();
true
} else {
false
}
}
}
impl Default for InterruptState {
fn default() -> Self {
Self::new()
}
}
pub fn setup_signal_handler() -> Result<InterruptState, String> {
let state = InterruptState::new();
ctrlc::set_handler(move || {
if INTERRUPTED.load(Ordering::Relaxed) {
eprintln!("\nForce exit.");
std::process::exit(130); }
INTERRUPTED.store(true, Ordering::SeqCst);
eprintln!("\nInterrupted. Completing current operation...");
})
.map_err(|e| format!("Failed to set signal handler: {}", e))?;
Ok(state)
}
pub fn report_interrupt_status(state: &InterruptState) {
if state.was_interrupted() {
let completed = state.files_completed();
let total = state.total_files();
if total > 0 {
eprintln!(
"Interrupted: Analyzed {}/{} files ({:.1}%)",
completed,
total,
(completed as f64 / total as f64) * 100.0
);
} else {
eprintln!("Interrupted: Analyzed {} files", completed);
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct InterruptMetadata {
pub interrupted: bool,
pub completed: usize,
pub total: usize,
pub percent_complete: f64,
}
impl InterruptMetadata {
pub fn from_state(state: &InterruptState) -> Self {
let completed = state.files_completed();
let total = state.total_files();
let percent = if total > 0 {
(completed as f64 / total as f64) * 100.0
} else {
0.0
};
Self {
interrupted: state.was_interrupted(),
completed,
total,
percent_complete: percent,
}
}
pub fn complete(total: usize) -> Self {
Self {
interrupted: false,
completed: total,
total,
percent_complete: 100.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_interrupt_state_new() {
let state = InterruptState::new();
assert!(!state.was_interrupted());
assert_eq!(state.files_completed(), 0);
assert_eq!(state.total_files(), 0);
}
#[test]
fn test_interrupt_state_tracking() {
let state = InterruptState::new();
state.set_total(100);
state.increment_completed();
state.increment_completed();
assert_eq!(state.files_completed(), 2);
assert_eq!(state.total_files(), 100);
}
#[test]
fn test_interrupt_state_mark_interrupted() {
let state = InterruptState::new();
assert!(!state.was_interrupted());
state.mark_interrupted();
assert!(state.was_interrupted());
}
#[test]
fn test_interrupt_metadata_from_state() {
let state = InterruptState::new();
state.set_total(100);
for _ in 0..50 {
state.increment_completed();
}
let metadata = InterruptMetadata::from_state(&state);
assert_eq!(metadata.completed, 50);
assert_eq!(metadata.total, 100);
assert!((metadata.percent_complete - 50.0).abs() < 0.01);
}
#[test]
fn test_interrupt_metadata_complete() {
let metadata = InterruptMetadata::complete(100);
assert!(!metadata.interrupted);
assert_eq!(metadata.completed, 100);
assert_eq!(metadata.total, 100);
assert_eq!(metadata.percent_complete, 100.0);
}
#[test]
fn test_reset_interrupted() {
reset_interrupted();
assert!(!is_interrupted());
}
}