#![allow(clippy::must_use_candidate)]
use std::{
future::Future,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use process_wrap::tokio::CommandWrap;
use watchexec_signals::Signal;
use crate::{command::Command, errors::SyncIoError, flag::Flag};
use super::{
messages::{Control, ControlMessage, Ticket},
priority::{Priority, PrioritySender},
JobTaskContext,
};
#[derive(Debug, Clone)]
pub struct Job {
pub(crate) command: Arc<Command>,
pub(crate) control_queue: PrioritySender,
pub(crate) gone: Flag,
pub(crate) running: Arc<AtomicBool>,
}
impl Job {
pub fn command(&self) -> Arc<Command> {
self.command.clone()
}
pub fn is_dead(&self) -> bool {
self.gone.raised()
}
pub fn is_running(&self) -> bool {
self.running.load(Ordering::Relaxed)
}
fn prepare_control(&self, control: Control) -> (Ticket, ControlMessage) {
let done = Flag::default();
(
Ticket {
job_gone: self.gone.clone(),
control_done: done.clone(),
},
ControlMessage { control, done },
)
}
pub(crate) fn send_controls<const N: usize>(
&self,
controls: [Control; N],
priority: Priority,
) -> Ticket {
if N == 0 || self.gone.raised() {
Ticket::cancelled()
} else if N == 1 {
let control = controls.into_iter().next().expect("UNWRAP: N > 0");
let (ticket, control) = self.prepare_control(control);
self.control_queue.send(control, priority);
ticket
} else {
let mut last_ticket = None;
for control in controls {
let (ticket, control) = self.prepare_control(control);
last_ticket = Some(ticket);
self.control_queue.send(control, priority);
}
last_ticket.expect("UNWRAP: N > 0")
}
}
pub fn control(&self, control: Control) -> Ticket {
self.send_controls([control], Priority::Normal)
}
pub fn start(&self) -> Ticket {
self.control(Control::Start)
}
pub fn stop(&self) -> Ticket {
self.control(Control::Stop)
}
pub fn stop_with_signal(&self, signal: Signal, grace: Duration) -> Ticket {
if cfg!(unix) {
self.control(Control::GracefulStop { signal, grace })
} else {
self.stop()
}
}
pub fn restart(&self) -> Ticket {
self.send_controls([Control::Stop, Control::Start], Priority::Normal)
}
pub fn restart_with_signal(&self, signal: Signal, grace: Duration) -> Ticket {
if cfg!(unix) {
self.send_controls(
[Control::GracefulStop { signal, grace }, Control::Start],
Priority::Normal,
)
} else {
self.restart()
}
}
pub fn try_restart(&self) -> Ticket {
self.control(Control::TryRestart)
}
pub fn try_restart_with_signal(&self, signal: Signal, grace: Duration) -> Ticket {
if cfg!(unix) {
self.control(Control::TryGracefulRestart { signal, grace })
} else {
self.try_restart()
}
}
pub fn signal(&self, sig: Signal) -> Ticket {
self.control(Control::Signal(sig))
}
pub fn delete(&self) -> Ticket {
self.send_controls([Control::Stop, Control::Delete], Priority::Normal)
}
pub fn delete_now(&self) -> Ticket {
self.send_controls([Control::Stop, Control::Delete], Priority::Urgent)
}
pub fn to_wait(&self) -> Ticket {
self.send_controls([Control::NextEnding], Priority::High)
}
pub fn run(&self, fun: impl FnOnce(&JobTaskContext<'_>) + Send + Sync + 'static) -> Ticket {
self.control(Control::SyncFunc(Box::new(fun)))
}
pub fn run_async(
&self,
fun: impl (FnOnce(&JobTaskContext<'_>) -> Box<dyn Future<Output = ()> + Send + Sync>)
+ Send
+ Sync
+ 'static,
) -> Ticket {
self.control(Control::AsyncFunc(Box::new(fun)))
}
pub fn set_spawn_hook(
&self,
fun: impl Fn(&mut CommandWrap, &JobTaskContext<'_>) + Send + Sync + 'static,
) -> Ticket {
self.control(Control::SetSyncSpawnHook(Arc::new(fun)))
}
pub fn set_spawn_async_hook(
&self,
fun: impl (Fn(&mut CommandWrap, &JobTaskContext<'_>) -> Box<dyn Future<Output = ()> + Send + Sync>)
+ Send
+ Sync
+ 'static,
) -> Ticket {
self.control(Control::SetAsyncSpawnHook(Arc::new(fun)))
}
pub fn unset_spawn_hook(&self) -> Ticket {
self.control(Control::UnsetSpawnHook)
}
pub fn set_spawn_fn(
&self,
fun: impl Fn(&mut tokio::process::Command) -> std::io::Result<tokio::process::Child>
+ Send
+ Sync
+ 'static,
) -> Ticket {
self.control(Control::SetSpawnFn(Arc::new(fun)))
}
pub fn unset_spawn_fn(&self) -> Ticket {
self.control(Control::ClearSpawnFn)
}
pub fn set_error_handler(&self, fun: impl Fn(SyncIoError) + Send + Sync + 'static) -> Ticket {
self.control(Control::SetSyncErrorHandler(Arc::new(fun)))
}
pub fn set_async_error_handler(
&self,
fun: impl (Fn(SyncIoError) -> Box<dyn Future<Output = ()> + Send + Sync>)
+ Send
+ Sync
+ 'static,
) -> Ticket {
self.control(Control::SetAsyncErrorHandler(Arc::new(fun)))
}
pub fn unset_error_handler(&self) -> Ticket {
self.control(Control::UnsetErrorHandler)
}
}