1use std::process::Child;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum JobState {
21 Running,
22 Stopped,
23 Done,
24}
25
26#[derive(Debug)]
28pub struct JobInfo {
29 pub id: usize,
30 pub pid: i32,
31 pub child: Option<Child>,
32 pub command: String,
33 pub state: JobState,
34 pub is_current: bool,
35}
36
37pub struct JobTable {
42 jobs: Vec<Option<JobInfo>>,
43 current_id: Option<usize>,
44 next_id: usize,
45}
46
47impl Default for JobTable {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl JobTable {
54 pub fn new() -> Self {
55 JobTable {
56 jobs: Vec::with_capacity(16),
57 current_id: None,
58 next_id: 1,
59 }
60 }
61
62 pub fn peek_next_id(&self) -> usize {
68 self.next_id
69 }
70
71 pub fn add_job(&mut self, child: Child, command: String, state: JobState) -> usize {
73 let id = self.next_id;
74 self.next_id += 1;
75
76 let pid = child.id() as i32;
77 let job = JobInfo {
78 id,
79 pid,
80 child: Some(child),
81 command,
82 state,
83 is_current: true,
84 };
85
86 if let Some(cur_id) = self.current_id {
88 if let Some(j) = self.get_mut_internal(cur_id) {
89 j.is_current = false;
90 }
91 }
92
93 let slot = self.get_free_slot();
95 if slot >= self.jobs.len() {
96 self.jobs.resize_with(slot + 1, || None);
97 }
98 self.jobs[slot] = Some(job);
99 self.current_id = Some(id);
100
101 id
102 }
103
104 pub fn add_pid_job(&mut self, pid: i32, command: String, state: JobState) -> usize {
109 let id = self.next_id;
110 self.next_id += 1;
111 let job = JobInfo {
112 id,
113 pid,
114 child: None,
115 command,
116 state,
117 is_current: true,
118 };
119 if let Some(cur_id) = self.current_id {
120 if let Some(j) = self.get_mut_internal(cur_id) {
121 j.is_current = false;
122 }
123 }
124 let slot = self.get_free_slot();
125 if slot >= self.jobs.len() {
126 self.jobs.resize_with(slot + 1, || None);
127 }
128 self.jobs[slot] = Some(job);
129 self.current_id = Some(id);
130 id
131 }
132
133 fn get_free_slot(&self) -> usize {
134 for (i, slot) in self.jobs.iter().enumerate() {
135 if slot.is_none() {
136 return i;
137 }
138 }
139 self.jobs.len()
140 }
141
142 fn get_mut_internal(&mut self, id: usize) -> Option<&mut JobInfo> {
143 self.jobs.iter_mut().flatten().find(|job| job.id == id)
144 }
145
146 pub fn get(&self, id: usize) -> Option<&JobInfo> {
148 self.jobs
149 .iter()
150 .flatten()
151 .find(|&job| job.id == id)
152 .map(|v| v as _)
153 }
154
155 pub fn get_mut(&mut self, id: usize) -> Option<&mut JobInfo> {
157 self.get_mut_internal(id)
158 }
159
160 pub fn remove(&mut self, id: usize) -> Option<JobInfo> {
162 for slot in self.jobs.iter_mut() {
163 if slot.as_ref().map(|j| j.id == id).unwrap_or(false) {
164 let job = slot.take();
165 if self.current_id == Some(id) {
166 self.current_id = None;
167 }
168 return job;
169 }
170 }
171 None
172 }
173
174 pub fn list(&self) -> Vec<&JobInfo> {
176 self.jobs.iter().filter_map(|j| j.as_ref()).collect()
177 }
178
179 pub fn iter(&self) -> impl Iterator<Item = (usize, &JobInfo)> {
181 self.jobs
182 .iter()
183 .filter_map(|j| j.as_ref().map(|job| (job.id, job)))
184 }
185
186 pub fn count(&self) -> usize {
188 self.jobs.iter().filter(|j| j.is_some()).count()
189 }
190
191 pub fn is_empty(&self) -> bool {
193 self.count() == 0
194 }
195
196 pub fn current(&self) -> Option<&JobInfo> {
198 self.current_id.and_then(|id| self.get(id))
199 }
200
201 pub fn reap_finished(&mut self) -> Vec<JobInfo> {
203 let mut finished = Vec::new();
204
205 for job in self.jobs.iter_mut().flatten() {
206 if let Some(ref mut child) = job.child {
207 match child.try_wait() {
209 Ok(Some(_status)) => {
210 job.state = JobState::Done;
212 }
213 Ok(None) => {
214 }
216 Err(_) => {
217 job.state = JobState::Done;
219 }
220 }
221 }
222 }
223
224 for slot in self.jobs.iter_mut() {
226 if slot
227 .as_ref()
228 .map(|j| j.state == JobState::Done)
229 .unwrap_or(false)
230 {
231 if let Some(job) = slot.take() {
232 finished.push(job);
233 }
234 }
235 }
236
237 finished
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_job_table_new() {
247 let table = JobTable::new();
248 assert!(table.is_empty());
249 }
250
251 #[test]
252 fn test_job_state_enum() {
253 let state = JobState::Running;
254 assert_eq!(state, JobState::Running);
255 assert_ne!(state, JobState::Stopped);
256 assert_ne!(state, JobState::Done);
257 }
258
259 #[test]
260 fn test_add_pid_job_assigns_id() {
261 let mut t = JobTable::new();
262 let id1 = t.add_pid_job(1234, "cmd1".into(), JobState::Running);
263 let id2 = t.add_pid_job(5678, "cmd2".into(), JobState::Running);
264 assert_ne!(id1, id2);
265 assert_eq!(t.list().len(), 2);
266 assert_eq!(t.current().map(|j| j.id), Some(id2));
267 }
268
269 #[test]
270 fn test_remove_drops_current() {
271 let mut t = JobTable::new();
272 let id = t.add_pid_job(99, "x".into(), JobState::Running);
273 assert!(t.remove(id).is_some());
274 assert!(t.is_empty());
275 assert!(t.current().is_none());
276 }
277}