use serde_json::Value;
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,
finished: bool,
result: Option<Value>,
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, result: Result<Value, String>) {
let mut s = self.inner.lock().unwrap();
s.finished = true;
match result {
Ok(value) => s.result = Some(value),
Err(error) => s.error = Some(error),
}
}
pub fn drain_new(&self) -> String {
let mut s = self.inner.lock().unwrap();
std::mem::take(&mut s.data)
}
pub fn status_parts(&self) -> (bool, Option<Value>, Option<String>) {
let s = self.inner.lock().unwrap();
(s.finished, s.result.clone(), s.error.clone())
}
}
struct BgTask {
tool_name: String,
handle: JoinHandle<()>,
buffer: TaskBuffer,
}
pub struct TaskSnapshot {
pub new_output: String,
pub tool_name: String,
pub finished: bool,
pub result: Option<Value>,
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,
tool_name: impl Into<String>,
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 {
tool_name: tool_name.into(),
handle,
buffer,
},
);
id
}
pub fn poll(&self, id: &str) -> Option<TaskSnapshot> {
let mut tasks = self.tasks.lock().unwrap();
let task = tasks.get(id)?;
let new_output = task.buffer.drain_new();
let (finished, result, error) = task.buffer.status_parts();
let snapshot = TaskSnapshot {
new_output,
tool_name: task.tool_name.clone(),
finished,
result,
error,
};
if snapshot.finished {
tasks.remove(id);
}
Some(snapshot)
}
pub fn kill(&self, id: &str) -> bool {
match self.tasks.lock().unwrap().remove(id) {
Some(task) => {
task.handle.abort();
true
}
None => false,
}
}
}