use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use crate::error::{Error, Result};
use crate::paths;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RigState {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_up: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_check: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_check_result: Option<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub services: HashMap<String, ServiceState>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub shared_paths: HashMap<String, SharedPathState>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceState {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pid: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub started_at: Option<String>,
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedPathState {
pub target: String,
pub created_at: String,
}
impl RigState {
pub fn load(rig_id: &str) -> Result<Self> {
let path = paths::rig_state_file(rig_id)?;
if !path.exists() {
return Ok(Self::default());
}
let content = fs::read_to_string(&path).map_err(|e| {
Error::internal_unexpected(format!(
"Failed to read rig state {}: {}",
path.display(),
e
))
})?;
if content.trim().is_empty() {
return Ok(Self::default());
}
serde_json::from_str(&content).map_err(|e| {
Error::validation_invalid_json(
e,
Some(format!("parse rig state {}", path.display())),
Some(content.chars().take(200).collect()),
)
})
}
pub fn save(&self, rig_id: &str) -> Result<()> {
let dir = paths::rig_state_dir(rig_id)?;
fs::create_dir_all(&dir).map_err(|e| {
Error::internal_unexpected(format!(
"Failed to create rig state dir {}: {}",
dir.display(),
e
))
})?;
let path = paths::rig_state_file(rig_id)?;
let json = serde_json::to_string_pretty(self).map_err(|e| {
Error::internal_unexpected(format!("Failed to serialize rig state: {}", e))
})?;
fs::write(&path, json).map_err(|e| {
Error::internal_unexpected(format!(
"Failed to write rig state {}: {}",
path.display(),
e
))
})?;
Ok(())
}
}
pub(crate) fn now_rfc3339() -> String {
chrono::Utc::now().to_rfc3339()
}
#[cfg(test)]
#[path = "../../../tests/core/rig/state_test.rs"]
mod state_test;