use std::process::Child;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum JobState {
Running,
Stopped,
Done,
}
#[derive(Debug)]
pub struct JobInfo {
pub id: usize,
pub pid: i32,
pub child: Option<Child>,
pub command: String,
pub state: JobState,
pub is_current: bool,
}
pub struct JobTable {
jobs: Vec<Option<JobInfo>>,
current_id: Option<usize>,
next_id: usize,
}
impl Default for JobTable {
fn default() -> Self {
Self::new()
}
}
impl JobTable {
pub fn new() -> Self {
JobTable {
jobs: Vec::with_capacity(16),
current_id: None,
next_id: 1,
}
}
pub fn peek_next_id(&self) -> usize {
self.next_id
}
pub fn add_job(&mut self, child: Child, command: String, state: JobState) -> usize {
let id = self.next_id;
self.next_id += 1;
let pid = child.id() as i32;
let job = JobInfo {
id,
pid,
child: Some(child),
command,
state,
is_current: true,
};
if let Some(cur_id) = self.current_id {
if let Some(j) = self.get_mut_internal(cur_id) {
j.is_current = false;
}
}
let slot = self.get_free_slot();
if slot >= self.jobs.len() {
self.jobs.resize_with(slot + 1, || None);
}
self.jobs[slot] = Some(job);
self.current_id = Some(id);
id
}
pub fn add_pid_job(&mut self, pid: i32, command: String, state: JobState) -> usize {
let id = self.next_id;
self.next_id += 1;
let job = JobInfo {
id,
pid,
child: None,
command,
state,
is_current: true,
};
if let Some(cur_id) = self.current_id {
if let Some(j) = self.get_mut_internal(cur_id) {
j.is_current = false;
}
}
let slot = self.get_free_slot();
if slot >= self.jobs.len() {
self.jobs.resize_with(slot + 1, || None);
}
self.jobs[slot] = Some(job);
self.current_id = Some(id);
id
}
fn get_free_slot(&self) -> usize {
for (i, slot) in self.jobs.iter().enumerate() {
if slot.is_none() {
return i;
}
}
self.jobs.len()
}
fn get_mut_internal(&mut self, id: usize) -> Option<&mut JobInfo> {
self.jobs.iter_mut().flatten().find(|job| job.id == id)
}
pub fn get(&self, id: usize) -> Option<&JobInfo> {
self.jobs
.iter()
.flatten()
.find(|&job| job.id == id)
.map(|v| v as _)
}
pub fn get_mut(&mut self, id: usize) -> Option<&mut JobInfo> {
self.get_mut_internal(id)
}
pub fn remove(&mut self, id: usize) -> Option<JobInfo> {
for slot in self.jobs.iter_mut() {
if slot.as_ref().map(|j| j.id == id).unwrap_or(false) {
let job = slot.take();
if self.current_id == Some(id) {
self.current_id = None;
}
return job;
}
}
None
}
pub fn list(&self) -> Vec<&JobInfo> {
self.jobs.iter().filter_map(|j| j.as_ref()).collect()
}
pub fn iter(&self) -> impl Iterator<Item = (usize, &JobInfo)> {
self.jobs
.iter()
.filter_map(|j| j.as_ref().map(|job| (job.id, job)))
}
pub fn count(&self) -> usize {
self.jobs.iter().filter(|j| j.is_some()).count()
}
pub fn is_empty(&self) -> bool {
self.count() == 0
}
pub fn current(&self) -> Option<&JobInfo> {
self.current_id.and_then(|id| self.get(id))
}
pub fn reap_finished(&mut self) -> Vec<JobInfo> {
let mut finished = Vec::new();
for job in self.jobs.iter_mut().flatten() {
if let Some(ref mut child) = job.child {
match child.try_wait() {
Ok(Some(_status)) => {
job.state = JobState::Done;
}
Ok(None) => {
}
Err(_) => {
job.state = JobState::Done;
}
}
}
}
for slot in self.jobs.iter_mut() {
if slot
.as_ref()
.map(|j| j.state == JobState::Done)
.unwrap_or(false)
{
if let Some(job) = slot.take() {
finished.push(job);
}
}
}
finished
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_job_table_new() {
let table = JobTable::new();
assert!(table.is_empty());
}
#[test]
fn test_job_state_enum() {
let state = JobState::Running;
assert_eq!(state, JobState::Running);
assert_ne!(state, JobState::Stopped);
assert_ne!(state, JobState::Done);
}
#[test]
fn test_add_pid_job_assigns_id() {
let mut t = JobTable::new();
let id1 = t.add_pid_job(1234, "cmd1".into(), JobState::Running);
let id2 = t.add_pid_job(5678, "cmd2".into(), JobState::Running);
assert_ne!(id1, id2);
assert_eq!(t.list().len(), 2);
assert_eq!(t.current().map(|j| j.id), Some(id2));
}
#[test]
fn test_remove_drops_current() {
let mut t = JobTable::new();
let id = t.add_pid_job(99, "x".into(), JobState::Running);
assert!(t.remove(id).is_some());
assert!(t.is_empty());
assert!(t.current().is_none());
}
}