use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::worklog::now_secs;
#[derive(Serialize, Deserialize, Clone)]
pub struct FleetEntry {
pub host: String,
pub cwd: Option<String>,
pub session: Option<String>,
pub last_status: Option<String>,
pub as_of: u64,
}
fn fleet_path() -> Result<PathBuf> {
let home = crate::sys::paths::home_dir()
.ok_or_else(|| anyhow::anyhow!("no home directory"))?;
let dir = home.join(".mars");
std::fs::create_dir_all(&dir)?;
crate::sys::fsperm::restrict_dir(&dir)?;
Ok(dir.join("fleet.json"))
}
pub fn fleet_load() -> Vec<FleetEntry> {
let mut v: Vec<FleetEntry> = fleet_path()
.ok()
.and_then(|p| std::fs::read_to_string(p).ok())
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default();
v.sort_by(|a, b| b.as_of.cmp(&a.as_of));
v
}
pub fn fleet_record(host: &str, cwd: Option<String>) {
let mut v = fleet_load();
match v.iter_mut().find(|e| e.host == host) {
Some(e) => {
e.as_of = now_secs();
if cwd.is_some() {
e.cwd = cwd;
}
}
None => v.push(FleetEntry {
host: host.to_string(),
cwd,
session: None,
last_status: None,
as_of: now_secs(),
}),
}
fleet_save(v);
}
pub fn fleet_status(host: &str, session: Option<String>, status: &str) {
let mut v = fleet_load();
match v.iter_mut().find(|e| e.host == host) {
Some(e) => {
e.as_of = now_secs();
e.last_status = Some(status.to_string());
if session.is_some() {
e.session = session;
}
}
None => v.push(FleetEntry {
host: host.to_string(),
cwd: None,
session,
last_status: Some(status.to_string()),
as_of: now_secs(),
}),
}
fleet_save(v);
}
fn fleet_save(mut v: Vec<FleetEntry>) {
v.sort_by(|a, b| b.as_of.cmp(&a.as_of));
v.truncate(50);
if let Ok(p) = fleet_path() {
if let Ok(s) = serde_json::to_string_pretty(&v) {
let _ = std::fs::write(p, s);
}
}
}
pub fn resolve_target(hosts: &[String], input: &str) -> Option<String> {
let t = input.trim();
if t.is_empty() || t == "q" {
return None;
}
if let Ok(n) = t.parse::<usize>() {
return hosts.get(n.checked_sub(1)?).cloned();
}
if let Some(h) = hosts.iter().find(|h| h.as_str() == t) {
return Some(h.clone());
}
let pre: Vec<&String> = hosts.iter().filter(|h| h.starts_with(t)).collect();
if pre.len() == 1 {
Some(pre[0].clone())
} else {
None
}
}