use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use uuid::Uuid;
pub fn status_dir() -> PathBuf {
#[cfg(test)]
{
if let Some(p) = test_override::get() {
return p;
}
}
crate::config::opencrabs_home().join("tmp").join("detached")
}
#[cfg(test)]
pub(crate) mod test_override {
use std::cell::RefCell;
use std::path::PathBuf;
thread_local! {
static DIR: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
}
pub fn set(p: PathBuf) {
DIR.with(|d| *d.borrow_mut() = Some(p));
}
pub fn get() -> Option<PathBuf> {
DIR.with(|d| d.borrow().clone())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetachedTaskStatus {
pub task_id: String,
pub session_id: String,
pub label: String,
pub command: String,
pub spawned_at: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub finished: Option<DetachedFinish>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetachedFinish {
pub success: bool,
pub code: i32,
pub elapsed_secs: f32,
pub output_bytes: usize,
}
fn path_for(task_id: Uuid) -> PathBuf {
status_dir().join(format!("{task_id}.json"))
}
pub fn write_started(task_id: Uuid, session_id: Uuid, label: &str, command: &str) {
let status = DetachedTaskStatus {
task_id: task_id.to_string(),
session_id: session_id.to_string(),
label: label.to_string(),
command: command.to_string(),
spawned_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0),
finished: None,
};
write_status(task_id, &status);
}
pub fn write_finished(
task_id: Uuid,
session_id: Uuid,
label: &str,
command: &str,
finish: DetachedFinish,
) {
let mut status = match fs::read_to_string(path_for(task_id))
.ok()
.and_then(|raw| serde_json::from_str::<DetachedTaskStatus>(&raw).ok())
{
Some(existing) => existing,
None => DetachedTaskStatus {
task_id: task_id.to_string(),
session_id: session_id.to_string(),
label: label.to_string(),
command: command.to_string(),
spawned_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0),
finished: None,
},
};
let _ = (session_id, label, command); status.finished = Some(finish);
write_status(task_id, &status);
}
fn write_status(task_id: Uuid, status: &DetachedTaskStatus) {
let dir = status_dir();
if let Err(e) = fs::create_dir_all(&dir) {
tracing::warn!(
target: "background_task",
"Could not create detached-status dir {}: {e}",
dir.display()
);
return;
}
match serde_json::to_string_pretty(status) {
Ok(body) => {
if let Err(e) = fs::write(path_for(task_id), body) {
tracing::warn!(
target: "background_task",
"Could not write detached status for {task_id}: {e}"
);
}
}
Err(e) => tracing::warn!(
target: "background_task",
"Could not serialize detached status for {task_id}: {e}"
),
}
}