1use std::borrow::Cow;
4use std::collections::VecDeque;
5use std::fmt::Display;
6
7use futures::FutureExt;
8
9use crate::{ExecutionResult, error, processes, sys, tasks, trace_categories, traps};
10
11pub(crate) type JobResult = (Job, Result<ExecutionResult, error::Error>);
12
13#[derive(Default)]
15pub struct JobManager {
16 pub jobs: Vec<Job>,
18}
19
20pub enum JobTask {
22 External(processes::ChildProcess),
24 Internal(tasks::TaskHandle<Result<ExecutionResult, error::Error>>),
26}
27
28pub enum JobTaskWaitResult {
30 Completed(ExecutionResult),
32 Stopped,
34}
35
36impl JobTask {
37 pub const fn is_external(&self) -> bool {
39 matches!(self, Self::External(_))
40 }
41
42 pub async fn wait(&mut self) -> Result<JobTaskWaitResult, error::Error> {
50 match self {
51 Self::External(process) => {
52 let wait_result = process.wait().await?;
53 match wait_result {
54 processes::ProcessWaitResult::Completed(output) => {
55 Ok(JobTaskWaitResult::Completed(output.into()))
56 }
57 processes::ProcessWaitResult::Stopped => Ok(JobTaskWaitResult::Stopped),
58 }
59 }
60 Self::Internal(handle) => Ok(JobTaskWaitResult::Completed(handle.await??)),
61 }
62 }
63
64 fn poll(&mut self) -> Option<Result<ExecutionResult, error::Error>> {
69 match self {
70 Self::External(process) => {
71 let check_result = process.poll();
72 check_result.map(|polled_result| polled_result.map(|output| output.into()))
73 }
74 Self::Internal(handle) => {
75 let checkable_handle = handle;
76 checkable_handle.now_or_never().and_then(|r| r.ok())
77 }
78 }
79 }
80}
81
82impl JobManager {
83 pub fn new() -> Self {
85 Self::default()
86 }
87
88 pub fn add_as_current(&mut self, mut job: Job) -> &Job {
100 for j in &mut self.jobs {
101 if matches!(j.annotation, JobAnnotation::Current) {
102 j.annotation = JobAnnotation::Previous;
103 break;
104 }
105 }
106
107 let id = self.jobs.len() + 1;
108 job.id = id;
109 job.annotation = JobAnnotation::Current;
110 self.jobs.push(job);
111
112 #[allow(clippy::unwrap_used, reason = "we just pushed an element")]
113 self.jobs.last().unwrap()
114 }
115
116 pub fn current_job(&self) -> Option<&Job> {
118 self.jobs
119 .iter()
120 .find(|j| matches!(j.annotation, JobAnnotation::Current))
121 }
122
123 pub fn current_job_mut(&mut self) -> Option<&mut Job> {
125 self.jobs
126 .iter_mut()
127 .find(|j| matches!(j.annotation, JobAnnotation::Current))
128 }
129
130 pub fn prev_job(&self) -> Option<&Job> {
132 self.jobs
133 .iter()
134 .find(|j| matches!(j.annotation, JobAnnotation::Previous))
135 }
136
137 pub fn prev_job_mut(&mut self) -> Option<&mut Job> {
139 self.jobs
140 .iter_mut()
141 .find(|j| matches!(j.annotation, JobAnnotation::Previous))
142 }
143
144 pub fn resolve_job_spec(&mut self, job_spec: &str) -> Option<&mut Job> {
150 let remainder = job_spec.strip_prefix('%')?;
151
152 match remainder {
153 "%" | "+" => self.current_job_mut(),
154 "-" => self.prev_job_mut(),
155 s if s.chars().all(char::is_numeric) => {
156 let id = s.parse::<usize>().ok()?;
157 self.jobs.iter_mut().find(|j| j.id == id)
158 }
159 _ => {
160 tracing::warn!(target: trace_categories::UNIMPLEMENTED, "unimplemented: job spec naming command: '{job_spec}'");
161 None
162 }
163 }
164 }
165
166 pub async fn wait_all(&mut self) -> Result<Vec<Job>, error::Error> {
173 for job in &mut self.jobs {
174 job.wait().await?;
175 }
176
177 Ok(self.sweep_completed_jobs())
178 }
179
180 pub fn poll(&mut self) -> Result<Vec<JobResult>, error::Error> {
186 let mut results = Vec::with_capacity(self.jobs.len());
187
188 let mut i = 0;
189 while i != self.jobs.len() {
190 if let Some(result) = self.jobs[i].poll_done()? {
191 let job = self.jobs.remove(i);
192 results.push((job, result));
193 } else if matches!(self.jobs[i].state, JobState::Done) {
194 results.push((self.jobs.remove(i), Ok(ExecutionResult::success())));
197 } else {
198 i += 1;
199 }
200 }
201
202 Ok(results)
203 }
204
205 fn sweep_completed_jobs(&mut self) -> Vec<Job> {
206 let mut completed_jobs = vec![];
207
208 let mut i = 0;
209 while i != self.jobs.len() {
210 if self.jobs[i].tasks.is_empty() {
211 completed_jobs.push(self.jobs.remove(i));
212 } else {
213 i += 1;
214 }
215 }
216
217 completed_jobs
218 }
219}
220
221#[derive(Clone)]
223pub enum JobState {
224 Unknown,
226 Running,
228 Stopped,
230 Done,
232}
233
234impl Display for JobState {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 match self {
237 Self::Unknown => write!(f, "Unknown"),
238 Self::Running => write!(f, "Running"),
239 Self::Stopped => write!(f, "Stopped"),
240 Self::Done => write!(f, "Done"),
241 }
242 }
243}
244
245#[derive(Clone)]
247pub enum JobAnnotation {
248 None,
250 Current,
252 Previous,
254}
255
256impl Display for JobAnnotation {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 match self {
259 Self::None => write!(f, ""),
260 Self::Current => write!(f, "+"),
261 Self::Previous => write!(f, "-"),
262 }
263 }
264}
265
266pub struct Job {
268 tasks: VecDeque<JobTask>,
270
271 annotation: JobAnnotation,
273
274 pub id: usize,
276
277 pub command_line: String,
279
280 pub state: JobState,
282}
283
284impl Display for Job {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 write!(
287 f,
288 "[{}]{:3}{}\t{}",
289 self.id, self.annotation, self.state, self.command_line
290 )
291 }
292}
293
294impl Job {
295 pub(crate) fn new<I>(tasks: I, command_line: String, state: JobState) -> Self
303 where
304 I: IntoIterator<Item = JobTask>,
305 {
306 Self {
307 id: 0,
308 tasks: tasks.into_iter().collect(),
309 annotation: JobAnnotation::None,
310 command_line,
311 state,
312 }
313 }
314
315 pub fn to_pid_style_string(&self) -> String {
317 let display_pid = self
318 .representative_pid()
319 .map_or(Cow::Borrowed("<pid unknown>"), |pid| {
320 Cow::Owned(pid.to_string())
321 });
322 std::format!("[{}]{}\t{}", self.id, self.annotation, display_pid)
323 }
324
325 pub fn annotation(&self) -> JobAnnotation {
327 self.annotation.clone()
328 }
329
330 pub fn command_name(&self) -> &str {
332 self.command_line
333 .split_ascii_whitespace()
334 .next()
335 .unwrap_or_default()
336 }
337
338 pub const fn is_current(&self) -> bool {
340 matches!(self.annotation, JobAnnotation::Current)
341 }
342
343 pub const fn is_prev(&self) -> bool {
345 matches!(self.annotation, JobAnnotation::Previous)
346 }
347
348 pub fn poll_done(
355 &mut self,
356 ) -> Result<Option<Result<ExecutionResult, error::Error>>, error::Error> {
357 let mut result: Option<Result<ExecutionResult, error::Error>> = None;
358
359 tracing::debug!(target: trace_categories::JOBS, "Polling job {} for completion...", self.id);
360
361 while !self.tasks.is_empty() {
362 let task = &mut self.tasks[0];
363 match task.poll() {
364 Some(r) => {
365 self.tasks.remove(0);
366 result = Some(r);
367 }
368 None => {
369 return Ok(None);
370 }
371 }
372 }
373
374 tracing::debug!(target: trace_categories::JOBS, "Job {} has completed.", self.id);
375
376 self.state = JobState::Done;
377
378 Ok(result)
379 }
380
381 pub async fn wait(&mut self) -> Result<ExecutionResult, error::Error> {
388 let mut result = ExecutionResult::success();
389
390 while let Some(task) = self.tasks.back_mut() {
391 match task.wait().await? {
392 JobTaskWaitResult::Completed(execution_result) => {
393 result = execution_result;
394 self.tasks.pop_back();
395 }
396 JobTaskWaitResult::Stopped => {
397 self.state = JobState::Stopped;
398 return Ok(ExecutionResult::stopped());
399 }
400 }
401 }
402
403 self.state = JobState::Done;
404
405 Ok(result)
406 }
407
408 pub fn move_to_background(&mut self) -> Result<(), error::Error> {
416 self.continue_job()
417 }
418
419 pub fn move_to_foreground(&mut self) -> Result<(), error::Error> {
426 if let Some(pgid) = self.own_process_group()
432 && let Err(err) = sys::terminal::move_to_foreground(pgid)
433 {
434 tracing::debug!(target: trace_categories::JOBS, "not giving job {} the terminal: {err}", self.id);
435 }
436
437 self.continue_job()
438 }
439
440 fn continue_job(&mut self) -> Result<(), error::Error> {
442 if matches!(self.state, JobState::Stopped) {
443 self.continue_processes()?;
444 self.state = JobState::Running;
445 } else {
446 let _ = self.continue_processes();
450 }
451
452 Ok(())
453 }
454
455 fn continue_processes(&self) -> Result<(), error::Error> {
458 if let Some(pgid) = self.own_process_group() {
459 sys::signal::continue_process_group(pgid)
460 } else if let Some(pid) = self.representative_pid() {
461 sys::signal::continue_process(pid)
462 } else {
463 Err(error::ErrorKind::FailedToSendSignal.into())
464 }
465 }
466
467 pub fn check_signalable(&self) -> Result<(), error::Error> {
477 if let Some(pgid) = self.own_process_group() {
478 sys::signal::check_process_group_signalable(pgid)
479 } else if let Some(pid) = self.representative_pid() {
480 sys::signal::check_signalable(pid)
481 } else {
482 Err(error::ErrorKind::FailedToSendSignal.into())
483 }
484 }
485
486 pub fn kill(&mut self, signal: traps::TrapSignal) -> Result<(), error::Error> {
500 if let Some(pgid) = self.own_process_group() {
501 sys::signal::kill_process_group(pgid, signal)?;
502 } else if let Some(pid) = self.representative_pid() {
503 sys::signal::kill_process(pid, signal)?;
504 } else {
505 return Err(error::ErrorKind::FailedToSendSignal.into());
506 }
507
508 if sys::signal::continue_stopped_on(signal) {
512 self.continue_job()?;
513 }
514
515 Ok(())
516 }
517
518 pub fn representative_pid(&self) -> Option<sys::process::ProcessId> {
520 for task in &self.tasks {
521 match task {
522 JobTask::External(p) => {
523 if let Some(pid) = p.pid() {
524 return Some(pid);
525 }
526 }
527 JobTask::Internal(_) => (),
528 }
529 }
530 None
531 }
532
533 pub fn process_group_id(&self) -> Option<sys::process::ProcessId> {
536 self.own_process_group()
537 .or_else(|| self.representative_pid())
538 }
539
540 fn own_process_group(&self) -> Option<sys::process::ProcessId> {
544 self.tasks.iter().find_map(|task| match task {
545 JobTask::External(child) => child.pgid(),
546 JobTask::Internal(_) => None,
547 })
548 }
549}