1use std::collections::HashMap;
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::{Arc, Mutex};
12use tokio::task::JoinHandle;
13
14#[derive(Default)]
15struct BufState {
16 data: String,
18 cursor: usize,
20 finished: bool,
21 exit_code: Option<i32>,
22 error: Option<String>,
24}
25
26#[derive(Clone, Default)]
28pub struct TaskBuffer {
29 inner: Arc<Mutex<BufState>>,
30}
31
32impl TaskBuffer {
33 pub fn new() -> Self {
34 Self::default()
35 }
36
37 pub fn append(&self, chunk: &str) {
39 self.inner.lock().unwrap().data.push_str(chunk);
40 }
41
42 pub fn finish(&self, exit_code: Option<i32>, error: Option<String>) {
44 let mut s = self.inner.lock().unwrap();
45 s.finished = true;
46 s.exit_code = exit_code;
47 s.error = error;
48 }
49
50 pub fn drain_new(&self) -> String {
52 let mut s = self.inner.lock().unwrap();
53 let new = s.data[s.cursor..].to_string();
54 s.cursor = s.data.len();
55 new
56 }
57
58 pub fn status_parts(&self) -> (bool, Option<i32>, Option<String>) {
60 let s = self.inner.lock().unwrap();
61 (s.finished, s.exit_code, s.error.clone())
62 }
63}
64
65struct BgTask {
66 handle: JoinHandle<()>,
67 buffer: TaskBuffer,
68}
69
70pub struct TaskSnapshot {
72 pub new_output: String,
74 pub finished: bool,
75 pub exit_code: Option<i32>,
76 pub error: Option<String>,
77}
78
79#[derive(Clone, Default)]
81pub struct TaskManager {
82 tasks: Arc<Mutex<HashMap<String, BgTask>>>,
83 counter: Arc<AtomicU64>,
84}
85
86impl TaskManager {
87 pub fn new() -> Self {
88 Self::default()
89 }
90
91 pub fn register(&self, handle: JoinHandle<()>, buffer: TaskBuffer) -> String {
93 let n = self.counter.fetch_add(1, Ordering::SeqCst);
94 let id = format!("task_{n}");
95 self.tasks
96 .lock()
97 .unwrap()
98 .insert(id.clone(), BgTask { handle, buffer });
99 id
100 }
101
102 pub fn poll(&self, id: &str) -> Option<TaskSnapshot> {
104 let tasks = self.tasks.lock().unwrap();
105 let task = tasks.get(id)?;
106 let new_output = task.buffer.drain_new();
107 let (finished, exit_code, error) = task.buffer.status_parts();
108 Some(TaskSnapshot {
109 new_output,
110 finished,
111 exit_code,
112 error,
113 })
114 }
115
116 pub fn kill(&self, id: &str) -> bool {
119 match self.tasks.lock().unwrap().remove(id) {
120 Some(task) => {
121 task.handle.abort();
122 true
123 }
124 None => false,
125 }
126 }
127}