use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use tokio::task::JoinHandle;
#[derive(Default)]
struct BufState {
data: String,
cursor: usize,
finished: bool,
exit_code: Option<i32>,
error: Option<String>,
}
#[derive(Clone, Default)]
pub struct TaskBuffer {
inner: Arc<Mutex<BufState>>,
}
impl TaskBuffer {
pub fn new() -> Self {
Self::default()
}
pub fn append(&self, chunk: &str) {
self.inner.lock().unwrap().data.push_str(chunk);
}
pub fn finish(&self, exit_code: Option<i32>, error: Option<String>) {
let mut s = self.inner.lock().unwrap();
s.finished = true;
s.exit_code = exit_code;
s.error = error;
}
pub fn drain_new(&self) -> String {
let mut s = self.inner.lock().unwrap();
let new = s.data[s.cursor..].to_string();
s.cursor = s.data.len();
new
}
pub fn status_parts(&self) -> (bool, Option<i32>, Option<String>) {
let s = self.inner.lock().unwrap();
(s.finished, s.exit_code, s.error.clone())
}
}
struct BgTask {
handle: JoinHandle<()>,
buffer: TaskBuffer,
}
pub struct TaskSnapshot {
pub new_output: String,
pub finished: bool,
pub exit_code: Option<i32>,
pub error: Option<String>,
}
#[derive(Clone, Default)]
pub struct TaskManager {
tasks: Arc<Mutex<HashMap<String, BgTask>>>,
counter: Arc<AtomicU64>,
}
impl TaskManager {
pub fn new() -> Self {
Self::default()
}
pub fn register(&self, handle: JoinHandle<()>, buffer: TaskBuffer) -> String {
let n = self.counter.fetch_add(1, Ordering::SeqCst);
let id = format!("task_{n}");
self.tasks
.lock()
.unwrap()
.insert(id.clone(), BgTask { handle, buffer });
id
}
pub fn poll(&self, id: &str) -> Option<TaskSnapshot> {
let tasks = self.tasks.lock().unwrap();
let task = tasks.get(id)?;
let new_output = task.buffer.drain_new();
let (finished, exit_code, error) = task.buffer.status_parts();
Some(TaskSnapshot {
new_output,
finished,
exit_code,
error,
})
}
pub fn kill(&self, id: &str) -> bool {
match self.tasks.lock().unwrap().remove(id) {
Some(task) => {
task.handle.abort();
true
}
None => false,
}
}
}