use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use super::{ExclusiveOption, SubCommand, Unselected};
use crate::global::GlobalOpts;
use crate::spawn::ParameterizedSpawn;
const MAX_INTERVAL_SECONDS: u64 = 2_678_400;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandMode {
execute: String,
}
impl ExclusiveOption for CommandMode {
fn inject_args(&self, command: &mut Command) {
command.arg("-e").arg(&self.execute);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TriggerMode {
trigger: String,
}
impl ExclusiveOption for TriggerMode {
fn inject_args(&self, command: &mut Command) {
command.arg("-t").arg(&self.trigger);
}
}
pub trait TaskTarget: ExclusiveOption {}
impl TaskTarget for CommandMode {}
impl TaskTarget for TriggerMode {}
#[derive(Debug, Clone, Default)]
pub struct BackGroundTask<M = Unselected> {
bin: PathBuf,
global_opts: GlobalOpts,
retries: Option<u64>,
detach: bool,
interval: Option<u64>,
runs: Option<u64>,
wait: Option<u64>,
mode: M,
}
impl BackGroundTask<Unselected> {
pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
Self {
bin: bin.into(),
global_opts,
..Default::default()
}
}
}
impl<M> BackGroundTask<M> {
pub fn get_bin(&self) -> &Path {
&self.bin
}
pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
self.bin = bin.into();
self
}
pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
self.set_bin(bin);
self
}
pub fn execute(self, command: impl Into<String>) -> BackGroundTask<CommandMode> {
BackGroundTask {
bin: self.bin,
global_opts: self.global_opts,
retries: self.retries,
detach: self.detach,
interval: self.interval,
runs: self.runs,
wait: self.wait,
mode: CommandMode {
execute: command.into(),
},
}
}
pub fn trigger(self, name: impl Into<String>) -> BackGroundTask<TriggerMode> {
BackGroundTask {
bin: self.bin,
global_opts: self.global_opts,
retries: self.retries,
detach: self.detach,
interval: self.interval,
runs: self.runs,
wait: self.wait,
mode: TriggerMode {
trigger: name.into(),
},
}
}
#[cfg_attr(
all(feature = "lt2018_2", not(feature = "lt2017_1")),
doc = "See [Global Options](GlobalOpts)."
)]
#[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
pub fn get_global_opts(&self) -> &GlobalOpts {
&self.global_opts
}
#[cfg_attr(
all(feature = "lt2018_2", not(feature = "lt2017_1")),
doc = "See [Global Options](GlobalOpts)."
)]
#[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
self.global_opts = v;
self
}
#[cfg_attr(
all(feature = "lt2018_2", not(feature = "lt2017_1")),
doc = "See [Global Options](GlobalOpts)."
)]
#[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
pub fn global_opts(mut self, v: GlobalOpts) -> Self {
self.global_opts = v;
self
}
pub fn get_retries(&self) -> Option<u64> {
self.retries
}
pub fn set_retries(&mut self, v: u64) -> &mut Self {
self.retries = Some(v);
self
}
pub fn retries(mut self, v: u64) -> Self {
self.retries = Some(v);
self
}
pub fn get_detach(&self) -> bool {
self.detach
}
pub fn set_detach(&mut self, v: bool) -> &mut Self {
self.detach = v;
self
}
pub fn detach(mut self, v: bool) -> Self {
self.detach = v;
self
}
pub fn get_interval(&self) -> Option<u64> {
self.interval
}
pub fn set_interval(&mut self, v: u64) -> &mut Self {
debug_assert!(
v <= MAX_INTERVAL_SECONDS,
"bgtask -i interval must not exceed {MAX_INTERVAL_SECONDS} seconds (31 days)"
);
self.interval = Some(v);
self
}
pub fn interval(mut self, v: u64) -> Self {
debug_assert!(
v <= MAX_INTERVAL_SECONDS,
"bgtask -i interval must not exceed {MAX_INTERVAL_SECONDS} seconds (31 days)"
);
self.interval = Some(v);
self
}
pub fn get_runs(&self) -> Option<u64> {
self.runs
}
pub fn set_runs(&mut self, v: u64) -> &mut Self {
self.runs = Some(v);
self
}
pub fn runs(mut self, v: u64) -> Self {
self.runs = Some(v);
self
}
pub fn get_wait(&self) -> Option<u64> {
self.wait
}
pub fn set_wait(&mut self, v: u64) -> &mut Self {
self.wait = Some(v);
self
}
pub fn wait(mut self, v: u64) -> Self {
self.wait = Some(v);
self
}
}
impl BackGroundTask<CommandMode> {
pub fn get_command(&self) -> &str {
&self.mode.execute
}
}
impl BackGroundTask<TriggerMode> {
pub fn get_trigger(&self) -> &str {
&self.mode.trigger
}
}
impl<M: ExclusiveOption> SubCommand for BackGroundTask<M> {
fn name(&self) -> &str {
"bgtask"
}
fn inject_local_args(&self, command: &mut Command) {
if let Some(retries) = self.retries {
command.arg("-b").arg(retries.to_string());
}
if self.detach {
command.arg("-d");
}
if let Some(interval) = self.interval {
command.arg("-i").arg(interval.to_string());
}
if let Some(runs) = self.runs {
command.arg("-m").arg(runs.to_string());
}
if let Some(wait) = self.wait {
command.arg("-w").arg(wait.to_string());
}
self.mode.inject_args(command);
}
fn global_opts(&self) -> Option<&GlobalOpts> {
Some(&self.global_opts)
}
}
impl<M: TaskTarget> ParameterizedSpawn<()> for BackGroundTask<M> {
type Output = Child;
type Error = std::io::Error;
fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
self.setup_command(&self.bin)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cmd::args_of;
#[test]
fn bin_accessors_update_executable_path() {
let mut command = BackGroundTask::new("p4", GlobalOpts::new()).trigger("verify");
assert_eq!(command.get_bin(), Path::new("p4"));
command.set_bin("/usr/local/bin/p4");
assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
let command = command.bin("p4.exe");
assert_eq!(command.get_bin(), Path::new("p4.exe"));
}
#[test]
fn execute_target_only() {
let task = BackGroundTask::new("p4", GlobalOpts::new()).execute("top -b -n 1");
assert_eq!(
args_of(&task.setup_command("p4")),
["bgtask", "-e", "top -b -n 1"]
);
assert_eq!(task.get_command(), "top -b -n 1");
}
#[test]
fn trigger_target_only() {
let task = BackGroundTask::new("p4", GlobalOpts::new()).trigger("log_checker");
assert_eq!(
args_of(&task.setup_command("p4")),
["bgtask", "-t", "log_checker"]
);
assert_eq!(task.get_trigger(), "log_checker");
}
#[test]
fn all_options_injected_in_syntax_order() {
let task = BackGroundTask::new("p4", GlobalOpts::new())
.retries(3)
.detach(true)
.interval(86_400)
.runs(5)
.wait(10)
.trigger("p4dstate");
assert_eq!(
args_of(&task.setup_command("p4")),
[
"bgtask", "-b", "3", "-d", "-i", "86400", "-m", "5", "-w", "10", "-t", "p4dstate"
]
);
}
#[test]
fn global_opts_are_injected_once() {
let task =
BackGroundTask::new("p4", GlobalOpts::new().port("localhost:1666")).execute("verify");
assert_eq!(
args_of(&task.setup_command("p4")),
["-p", "localhost:1666", "bgtask", "-e", "verify"]
);
}
#[test]
fn switching_targets_replaces_selection() {
let task = BackGroundTask::new("p4", GlobalOpts::new())
.runs(2)
.execute("old command")
.trigger("verify");
assert_eq!(
args_of(&task.setup_command("p4")),
["bgtask", "-m", "2", "-t", "verify"]
);
assert_eq!(task.get_trigger(), "verify");
let task = task.execute("new command");
assert_eq!(
args_of(&task.setup_command("p4")),
["bgtask", "-m", "2", "-e", "new command"]
);
assert_eq!(task.get_command(), "new command");
}
#[test]
fn interval_accepts_the_document_maximum() {
let task = BackGroundTask::new("p4", GlobalOpts::new())
.interval(MAX_INTERVAL_SECONDS)
.trigger("verify");
assert_eq!(
args_of(&task.setup_command("p4")),
["bgtask", "-i", "2678400", "-t", "verify"]
);
}
#[test]
#[should_panic(expected = "31 days")]
fn interval_above_maximum_panics_in_debug() {
let _ = BackGroundTask::new("p4", GlobalOpts::new()).interval(MAX_INTERVAL_SECONDS + 1);
}
#[test]
fn setters_update_the_same_command() {
let mut task = BackGroundTask::new("p4", GlobalOpts::new()).trigger("verify");
task.set_retries(4).set_detach(true).set_wait(30);
assert_eq!(task.get_retries(), Some(4));
assert!(task.get_detach());
assert_eq!(task.get_wait(), Some(30));
assert_eq!(
args_of(&task.setup_command("p4")),
["bgtask", "-b", "4", "-d", "-w", "30", "-t", "verify"]
);
}
}