use std::{sync::Arc, time::Instant};
#[cfg(not(test))]
use process_wrap::tokio::ChildWrapper;
use process_wrap::tokio::CommandWrap;
use tracing::trace;
use watchexec_events::ProcessEnd;
use crate::command::Command;
use super::task::SpawnFn;
#[derive(Debug)]
#[cfg_attr(test, derive(Clone))]
pub enum CommandState {
Pending,
Running {
#[cfg(test)]
child: super::TestChild,
#[cfg(not(test))]
child: Box<dyn ChildWrapper>,
started: Instant,
},
Finished {
status: ProcessEnd,
started: Instant,
finished: Instant,
},
}
impl CommandState {
#[must_use]
pub const fn is_pending(&self) -> bool {
matches!(self, Self::Pending)
}
#[must_use]
pub const fn is_running(&self) -> bool {
matches!(self, Self::Running { .. })
}
#[must_use]
pub const fn is_finished(&self) -> bool {
matches!(self, Self::Finished { .. })
}
#[cfg_attr(test, allow(unused_mut, unused_variables))]
pub(crate) fn spawn(
&mut self,
command: Arc<Command>,
mut spawnable: CommandWrap,
spawn_fn: Option<&SpawnFn>,
) -> std::io::Result<bool> {
if let Self::Running { .. } = self {
trace!("command running, not spawning again");
return Ok(false);
}
trace!(?command, "spawning command");
#[cfg(test)]
let child = super::TestChild::new(command)?;
#[cfg(not(test))]
let child = if let Some(f) = spawn_fn {
spawnable.spawn_with(|cmd| f(cmd))?
} else {
spawnable.spawn()?
};
*self = Self::Running {
child,
started: Instant::now(),
};
Ok(true)
}
#[must_use]
pub(crate) fn reset(&mut self) -> Self {
trace!(?self, "resetting command state");
match self {
Self::Pending => Self::Pending,
Self::Finished {
status,
started,
finished,
..
} => {
let copy = Self::Finished {
status: *status,
started: *started,
finished: *finished,
};
*self = Self::Pending;
copy
}
Self::Running { started, .. } => {
let copy = Self::Finished {
status: ProcessEnd::Continued,
started: *started,
finished: Instant::now(),
};
*self = Self::Pending;
copy
}
}
}
pub(crate) async fn wait(&mut self) -> std::io::Result<bool> {
if let Self::Running { child, started } = self {
let end = child.wait().await?;
*self = Self::Finished {
status: end.into(),
started: *started,
finished: Instant::now(),
};
Ok(true)
} else {
Ok(false)
}
}
}