use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{PoisonError, RwLock};
use std::time::SystemTime;
use super::errors::{InstanceError, InstanceResult};
pub(crate) struct GameInstance {
pub pid: u32,
pub instance_name: String,
#[allow(dead_code)]
pub version: String,
#[allow(dead_code)]
pub username: String,
#[allow(dead_code)]
pub game_dir: PathBuf,
#[allow(dead_code)]
pub started_at: SystemTime,
}
pub(crate) struct InstanceManager {
instances: RwLock<HashMap<u32, GameInstance>>,
}
pub(crate) static INSTANCE_MANAGER: Lazy<InstanceManager> = Lazy::new(InstanceManager::new);
impl InstanceManager {
pub fn new() -> Self {
Self {
instances: RwLock::new(HashMap::new()),
}
}
pub fn get_pid(&self, instance_name: &str) -> Option<u32> {
let instances = self
.instances
.read()
.unwrap_or_else(PoisonError::into_inner);
instances
.values()
.find(|inst| inst.instance_name == instance_name)
.map(|inst| inst.pid)
}
pub fn get_pids(&self, instance_name: &str) -> Vec<u32> {
let instances = self
.instances
.read()
.unwrap_or_else(PoisonError::into_inner);
instances
.values()
.filter(|inst| inst.instance_name == instance_name)
.map(|inst| inst.pid)
.collect()
}
pub fn is_alive(&self, pid: u32) -> bool {
let instances = self
.instances
.read()
.unwrap_or_else(PoisonError::into_inner);
instances.contains_key(&pid)
}
pub async fn register_instance(&self, instance: GameInstance) -> InstanceResult<()> {
let mut instances = self
.instances
.write()
.unwrap_or_else(PoisonError::into_inner);
if let Some(existing) = instances.get(&instance.pid) {
return Err(InstanceError::DuplicatePid {
pid: instance.pid,
existing_instance: existing.instance_name.clone(),
});
}
instances.insert(instance.pid, instance);
Ok(())
}
pub async fn unregister_instance(&self, pid: u32) {
let mut instances = self
.instances
.write()
.unwrap_or_else(PoisonError::into_inner);
instances.remove(&pid);
}
pub async fn close_instance(&self, pid: u32) -> InstanceResult<()> {
if !self
.instances
.read()
.unwrap_or_else(PoisonError::into_inner)
.contains_key(&pid)
{
return Err(InstanceError::NotFound { pid });
}
#[cfg(target_os = "windows")]
{
use std::process::Command;
let output = Command::new("taskkill")
.args(&["/PID", &pid.to_string(), "/F"])
.output()?;
if !output.status.success() {
return Err(InstanceError::KillFailed {
pid,
reason: String::from_utf8_lossy(&output.stderr).trim().to_string(),
});
}
}
#[cfg(not(target_os = "windows"))]
{
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
kill(Pid::from_raw(pid as i32), Signal::SIGTERM).map_err(|err| {
InstanceError::KillFailed {
pid,
reason: err.to_string(),
}
})?;
}
self.instances
.write()
.unwrap_or_else(PoisonError::into_inner)
.remove(&pid);
lighty_core::trace_info!(pid = pid, "Instance killed");
Ok(())
}
}
#[cfg(unix)]
pub(crate) fn process_is_running(pid: u32) -> bool {
use nix::errno::Errno;
use nix::sys::signal::kill;
use nix::unistd::Pid;
!matches!(kill(Pid::from_raw(pid as i32), None), Err(Errno::ESRCH))
}