use super::*;
pub(super) fn load_runtime_state_or_default(
state_file: &Path,
config_file: &Path,
profile: Option<String>,
) -> Result<RuntimeState, String> {
if state_file.exists() {
load_runtime_state(state_file)
} else {
Ok(RuntimeState {
api_version: ORCHESTRATION_RUNTIME_STATE_API_VERSION.to_string(),
config_file: config_file.to_path_buf(),
profile,
updated_at_unix_ms: now_unix_ms(),
processes: Vec::new(),
})
}
}
pub(super) fn load_runtime_state(state_file: &Path) -> Result<RuntimeState, String> {
let content = std::fs::read_to_string(state_file)
.map_err(|err| format!("read runtime state {} failed: {err}", state_file.display()))?;
serde_json::from_str::<RuntimeState>(&content)
.map_err(|err| format!("parse runtime state {} failed: {err}", state_file.display()))
}
pub(super) fn save_runtime_state(state_file: &Path, state: &RuntimeState) -> Result<(), String> {
if let Some(parent) = state_file.parent()
&& !parent.as_os_str().is_empty()
{
std::fs::create_dir_all(parent).map_err(|err| {
format!(
"create runtime state directory {} failed: {err}",
parent.display()
)
})?;
}
let payload = serde_json::to_string_pretty(state)
.map_err(|err| format!("serialize runtime state failed: {err}"))?;
std::fs::write(state_file, format!("{payload}\n"))
.map_err(|err| format!("write runtime state {} failed: {err}", state_file.display()))
}
pub(super) fn sanitize_name(raw: &str) -> String {
raw.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
ch
} else {
'_'
}
})
.collect()
}
pub(super) fn now_unix_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|dur| dur.as_millis() as u64)
.unwrap_or(0)
}
pub(super) fn process_alive(pid: u32) -> bool {
#[cfg(unix)]
{
Command::new("kill")
.args(["-0", &pid.to_string()])
.status()
.map(|status| status.success())
.unwrap_or(false)
}
#[cfg(windows)]
{
Command::new("cmd")
.args([
"/C",
&format!("tasklist /FI \"PID eq {pid}\" | findstr {pid}"),
])
.status()
.map(|status| status.success())
.unwrap_or(false)
}
}
pub(super) fn terminate_process(pid: u32) -> Result<(), String> {
#[cfg(unix)]
{
Command::new("kill")
.args(["-TERM", &pid.to_string()])
.status()
.map_err(|err| format!("send TERM to pid {pid} failed: {err}"))?;
for _ in 0..20 {
if !process_alive(pid) {
return Ok(());
}
std::thread::sleep(Duration::from_millis(50));
}
Command::new("kill")
.args(["-KILL", &pid.to_string()])
.status()
.map_err(|err| format!("send KILL to pid {pid} failed: {err}"))?;
Ok(())
}
#[cfg(windows)]
{
Command::new("cmd")
.args(["/C", &format!("taskkill /PID {pid} /T /F")])
.status()
.map_err(|err| format!("taskkill pid {pid} failed: {err}"))?;
Ok(())
}
}