use crate::{Config, WallSwitchError, WallSwitchResult, get_config_path};
use std::{fs, path::Path, path::PathBuf, process, thread::sleep, time::Duration};
use sysinfo::{Pid, ProcessesToUpdate, System};
pub fn kill_other_instances(config: &Config) -> WallSwitchResult<()> {
if config.dry_run {
if config.verbose {
println!("[DRY-RUN] Skipping single-instance locking and termination.");
}
return Ok(());
}
let pid_path = get_pid_path()?;
let current_pid = process::id();
if let Some(old_pid_u32) = read_pid_file(&pid_path)?
&& old_pid_u32 != current_pid
{
let mut sys = System::new();
let old_pid = Pid::from_u32(old_pid_u32);
sys.refresh_processes(ProcessesToUpdate::Some(&[old_pid]), true);
if let Some(process) = sys.process(old_pid) {
let process_name = process.name().to_string_lossy().to_lowercase();
if process_name.contains("wallswitch") {
if config.verbose {
println!("PID file: {}", pid_path.display());
println!(
"Terminating previous instance found in PID file (PID: {})...\n",
old_pid_u32
);
}
process.kill();
sleep(Duration::from_millis(100));
} else if config.verbose {
println!(
"PID {} is active but process name {:?} does not match. Skipping termination.",
old_pid_u32, process_name
);
}
}
}
if let Some(parent) = pid_path.parent() {
fs::create_dir_all(parent).map_err(|io_error| WallSwitchError::IOError {
path: parent.to_path_buf(),
io_error,
})?;
}
fs::write(&pid_path, current_pid.to_string()).map_err(|io_error| WallSwitchError::IOError {
path: pid_path.clone(),
io_error,
})?;
Ok(())
}
fn read_pid_file(path: &Path) -> WallSwitchResult<Option<u32>> {
if !path.exists() {
return Ok(None);
}
let content = fs::read_to_string(path).map_err(|io_error| WallSwitchError::IOError {
path: path.to_path_buf(),
io_error,
})?;
let pid = content.trim().parse::<u32>().ok();
Ok(pid)
}
fn get_pid_path() -> WallSwitchResult<PathBuf> {
let mut path = get_config_path()?;
path.set_file_name("wallswitch.pid");
Ok(path)
}
#[cfg(test)]
mod tests_pids {
use super::*;
#[test]
fn test_read_pid_file_missing() {
let temp_dir = std::env::temp_dir().join("wallswitch_test_missing_err");
let non_existent = temp_dir.join("non_existent.pid");
let result = read_pid_file(&non_existent);
assert!(result.is_ok());
assert_eq!(result.unwrap(), None);
}
#[test]
fn test_read_pid_file_valid() {
let temp_dir = std::env::temp_dir().join("wallswitch_test_valid_err");
fs::create_dir_all(&temp_dir).unwrap();
let pid_file = temp_dir.join("test.pid");
fs::write(&pid_file, "12345").unwrap();
let result = read_pid_file(&pid_file);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(12345));
let _ = fs::remove_dir_all(temp_dir);
}
#[test]
fn test_read_pid_file_invalid_content() {
let temp_dir = std::env::temp_dir().join("wallswitch_test_invalid_err");
fs::create_dir_all(&temp_dir).unwrap();
let pid_file = temp_dir.join("invalid.pid");
fs::write(&pid_file, "not_a_number").unwrap();
let result = read_pid_file(&pid_file);
assert!(result.is_ok());
assert_eq!(result.unwrap(), None);
let _ = fs::remove_dir_all(temp_dir);
}
#[test]
fn test_kill_other_instances_creates_valid_pid_file() {
let temp_dir = std::env::temp_dir().join("wallswitch_test_integration");
fs::create_dir_all(&temp_dir).unwrap();
let config = Config {
dry_run: false,
verbose: false,
..Default::default()
};
let result = kill_other_instances(&config);
assert!(result.is_ok());
let pid_path = get_pid_path().unwrap();
assert!(pid_path.exists());
let written_pid = fs::read_to_string(&pid_path).unwrap();
let parsed: u32 = written_pid.trim().parse().unwrap();
assert_eq!(parsed, std::process::id());
let _ = fs::remove_dir_all(temp_dir);
}
}