use std::process::Child;
use std::sync::Mutex;
use crate::ported::jobs::{deletejob, CURJOB, MAXJOB, PREVJOB, THISJOB};
use crate::ported::jobs::stat;
use crate::ported::zsh_h::job;
pub fn printjob_delete_tail(tab: &mut [job], idx: usize) {
if idx >= tab.len() || (tab[idx].stat & stat::DONE) == 0 {
return;
}
deletejob(&mut tab[idx], false); let mut cj = CURJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap();
let mut pj = PREVJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap();
if *cj == idx as i32 {
*cj = *pj;
*pj = idx as i32;
}
let need_setprev = *pj == idx as i32; drop(cj);
drop(pj);
if need_setprev {
setprevjob_locked(tab); }
}
fn setprevjob_locked(tab: &[job]) {
let maxjob = *MAXJOB
.get_or_init(|| Mutex::new(0))
.lock()
.expect("maxjob poisoned");
let curjob = *CURJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap();
let thisjob = *THISJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap();
let pick = |want_stopped: bool| -> i32 {
for i in (1..=maxjob).rev() {
if i >= tab.len() {
continue;
}
let j = &tab[i];
let stat_ok = if want_stopped {
(j.stat & (stat::INUSE | stat::STOPPED)) == (stat::INUSE | stat::STOPPED)
} else {
(j.stat & stat::INUSE) != 0
};
if stat_ok
&& (j.stat & stat::SUBJOB) == 0
&& i as i32 != curjob
&& i as i32 != thisjob
{
return i as i32;
}
}
-1
};
let mut found = pick(true); if found < 0 {
found = pick(false); }
*PREVJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap() = found; }
#[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 _g = crate::test_util::global_state_lock();
let table = JobTable::new();
assert!(table.is_empty());
}
#[test]
fn test_job_state_enum() {
let _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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());
}
}