use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
pub const PROGRESS_FILE: &str = "quality-progress.json";
pub const LOG_FILE: &str = "quality.log";
pub const PROGRESS_ENV: &str = "HELIOS_QUALITY_PROGRESS_FILE";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityProgress {
pub pid: u32,
pub started_at_secs: u64,
pub completed_at_secs: Option<u64>,
pub log_path: String,
pub source_root: String,
}
pub fn now_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
pub fn progress_path(kb_dir: &Path) -> PathBuf {
kb_dir.join(PROGRESS_FILE)
}
pub fn log_path(kb_dir: &Path) -> PathBuf {
kb_dir.join(LOG_FILE)
}
pub fn write(path: &Path, p: &QualityProgress) -> Result<()> {
let json = serde_json::to_string_pretty(p).context("serialise QualityProgress")?;
std::fs::write(path, json).with_context(|| format!("write {}", path.display()))
}
pub fn read(path: &Path) -> Result<Option<QualityProgress>> {
if !path.exists() {
return Ok(None);
}
let body = std::fs::read_to_string(path).with_context(|| format!("read {}", path.display()))?;
let p: QualityProgress =
serde_json::from_str(&body).with_context(|| format!("parse {}", path.display()))?;
Ok(Some(p))
}
pub fn finalize(path: &Path) -> Result<()> {
if let Some(mut p) = read(path)? {
p.completed_at_secs = Some(now_secs());
write(path, &p)?;
}
Ok(())
}
pub fn pid_alive(pid: u32) -> bool {
#[cfg(target_os = "linux")]
{
std::path::Path::new(&format!("/proc/{pid}")).exists()
}
#[cfg(all(unix, not(target_os = "linux")))]
{
unsafe { libc::kill(pid as i32, 0) == 0 }
}
#[cfg(not(unix))]
{
let _ = pid;
false
}
}
pub enum Phase {
NotStarted,
Running { p: QualityProgress, alive: bool },
Complete { p: QualityProgress },
}
pub fn classify(p: Option<QualityProgress>) -> Phase {
match p {
None => Phase::NotStarted,
Some(p) if p.completed_at_secs.is_some() => Phase::Complete { p },
Some(p) => {
let alive = pid_alive(p.pid);
Phase::Running { p, alive }
}
}
}
pub fn fmt_duration_secs(s: u64) -> String {
let m = s / 60;
let r = s % 60;
if m == 0 {
format!("{r} s")
} else if m < 60 {
format!("{m} m {r} s")
} else {
let h = m / 60;
let m = m % 60;
format!("{h} h {m} m")
}
}