1use std::path::{Path, PathBuf};
2use std::process::{Child, Command, Stdio};
3
4use super::{ExclusiveOption, SubCommand, Unselected};
5
6use crate::global::GlobalOpts;
7use crate::spawn::ParameterizedSpawn;
8
9const MAX_INTERVAL_SECONDS: u64 = 2_678_400;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct CommandMode {
23 execute: String,
24}
25
26impl ExclusiveOption for CommandMode {
27 fn inject_args(&self, command: &mut Command) {
28 command.arg("-e").arg(&self.execute);
29 }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct TriggerMode {
39 trigger: String,
40}
41
42impl ExclusiveOption for TriggerMode {
43 fn inject_args(&self, command: &mut Command) {
44 command.arg("-t").arg(&self.trigger);
45 }
46}
47
48pub trait TaskTarget: ExclusiveOption {}
54
55impl TaskTarget for CommandMode {}
56impl TaskTarget for TriggerMode {}
57
58#[derive(Debug, Clone, Default)]
80pub struct BackGroundTask<M = Unselected> {
81 bin: PathBuf,
82
83 global_opts: GlobalOpts,
84
85 retries: Option<u64>,
87
88 detach: bool,
90
91 interval: Option<u64>,
93
94 runs: Option<u64>,
96
97 wait: Option<u64>,
99
100 mode: M,
101}
102
103impl BackGroundTask<Unselected> {
104 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
110 Self {
111 bin: bin.into(),
112 global_opts,
113 ..Default::default()
114 }
115 }
116}
117
118impl<M> BackGroundTask<M> {
119 pub fn get_bin(&self) -> &Path {
121 &self.bin
122 }
123
124 pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
126 self.bin = bin.into();
127 self
128 }
129
130 pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
132 self.set_bin(bin);
133 self
134 }
135
136 pub fn execute(self, command: impl Into<String>) -> BackGroundTask<CommandMode> {
142 BackGroundTask {
143 bin: self.bin,
144 global_opts: self.global_opts,
145 retries: self.retries,
146 detach: self.detach,
147 interval: self.interval,
148 runs: self.runs,
149 wait: self.wait,
150 mode: CommandMode {
151 execute: command.into(),
152 },
153 }
154 }
155
156 pub fn trigger(self, name: impl Into<String>) -> BackGroundTask<TriggerMode> {
163 BackGroundTask {
164 bin: self.bin,
165 global_opts: self.global_opts,
166 retries: self.retries,
167 detach: self.detach,
168 interval: self.interval,
169 runs: self.runs,
170 wait: self.wait,
171 mode: TriggerMode {
172 trigger: name.into(),
173 },
174 }
175 }
176
177 #[cfg_attr(
182 all(feature = "lt2018_2", not(feature = "lt2017_1")),
183 doc = "See [Global Options](GlobalOpts)."
184 )]
185 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
186 pub fn get_global_opts(&self) -> &GlobalOpts {
187 &self.global_opts
188 }
189
190 #[cfg_attr(
195 all(feature = "lt2018_2", not(feature = "lt2017_1")),
196 doc = "See [Global Options](GlobalOpts)."
197 )]
198 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
199 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
200 self.global_opts = v;
201 self
202 }
203
204 #[cfg_attr(
209 all(feature = "lt2018_2", not(feature = "lt2017_1")),
210 doc = "See [Global Options](GlobalOpts)."
211 )]
212 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
213 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
214 self.global_opts = v;
215 self
216 }
217
218 pub fn get_retries(&self) -> Option<u64> {
225 self.retries
226 }
227
228 pub fn set_retries(&mut self, v: u64) -> &mut Self {
235 self.retries = Some(v);
236 self
237 }
238
239 pub fn retries(mut self, v: u64) -> Self {
246 self.retries = Some(v);
247 self
248 }
249
250 pub fn get_detach(&self) -> bool {
258 self.detach
259 }
260
261 pub fn set_detach(&mut self, v: bool) -> &mut Self {
269 self.detach = v;
270 self
271 }
272
273 pub fn detach(mut self, v: bool) -> Self {
281 self.detach = v;
282 self
283 }
284
285 pub fn get_interval(&self) -> Option<u64> {
292 self.interval
293 }
294
295 pub fn set_interval(&mut self, v: u64) -> &mut Self {
302 debug_assert!(
303 v <= MAX_INTERVAL_SECONDS,
304 "bgtask -i interval must not exceed {MAX_INTERVAL_SECONDS} seconds (31 days)"
305 );
306 self.interval = Some(v);
307 self
308 }
309
310 pub fn interval(mut self, v: u64) -> Self {
317 debug_assert!(
318 v <= MAX_INTERVAL_SECONDS,
319 "bgtask -i interval must not exceed {MAX_INTERVAL_SECONDS} seconds (31 days)"
320 );
321 self.interval = Some(v);
322 self
323 }
324
325 pub fn get_runs(&self) -> Option<u64> {
331 self.runs
332 }
333
334 pub fn set_runs(&mut self, v: u64) -> &mut Self {
340 self.runs = Some(v);
341 self
342 }
343
344 pub fn runs(mut self, v: u64) -> Self {
350 self.runs = Some(v);
351 self
352 }
353
354 pub fn get_wait(&self) -> Option<u64> {
361 self.wait
362 }
363
364 pub fn set_wait(&mut self, v: u64) -> &mut Self {
371 self.wait = Some(v);
372 self
373 }
374
375 pub fn wait(mut self, v: u64) -> Self {
382 self.wait = Some(v);
383 self
384 }
385}
386
387impl BackGroundTask<CommandMode> {
388 pub fn get_command(&self) -> &str {
390 &self.mode.execute
391 }
392}
393
394impl BackGroundTask<TriggerMode> {
395 pub fn get_trigger(&self) -> &str {
397 &self.mode.trigger
398 }
399}
400
401impl<M: ExclusiveOption> SubCommand for BackGroundTask<M> {
406 fn name(&self) -> &str {
407 "bgtask"
408 }
409
410 fn inject_local_args(&self, command: &mut Command) {
411 if let Some(retries) = self.retries {
412 command.arg("-b").arg(retries.to_string());
413 }
414
415 if self.detach {
416 command.arg("-d");
417 }
418
419 if let Some(interval) = self.interval {
420 command.arg("-i").arg(interval.to_string());
421 }
422
423 if let Some(runs) = self.runs {
424 command.arg("-m").arg(runs.to_string());
425 }
426
427 if let Some(wait) = self.wait {
428 command.arg("-w").arg(wait.to_string());
429 }
430
431 self.mode.inject_args(command);
432 }
433
434 fn global_opts(&self) -> Option<&GlobalOpts> {
435 Some(&self.global_opts)
436 }
437}
438
439impl<M: TaskTarget> ParameterizedSpawn<()> for BackGroundTask<M> {
444 type Output = Child;
445 type Error = std::io::Error;
446
447 fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
451 self.setup_command(&self.bin)
452 .stdout(Stdio::piped())
453 .stderr(Stdio::piped())
454 .spawn()
455 }
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461 use crate::cmd::args_of;
462
463 #[test]
464 fn bin_accessors_update_executable_path() {
465 let mut command = BackGroundTask::new("p4", GlobalOpts::new()).trigger("verify");
466 assert_eq!(command.get_bin(), Path::new("p4"));
467
468 command.set_bin("/usr/local/bin/p4");
469 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
470
471 let command = command.bin("p4.exe");
472 assert_eq!(command.get_bin(), Path::new("p4.exe"));
473 }
474
475 #[test]
478 fn execute_target_only() {
479 let task = BackGroundTask::new("p4", GlobalOpts::new()).execute("top -b -n 1");
480
481 assert_eq!(
482 args_of(&task.setup_command("p4")),
483 ["bgtask", "-e", "top -b -n 1"]
484 );
485 assert_eq!(task.get_command(), "top -b -n 1");
486 }
487
488 #[test]
489 fn trigger_target_only() {
490 let task = BackGroundTask::new("p4", GlobalOpts::new()).trigger("log_checker");
491
492 assert_eq!(
493 args_of(&task.setup_command("p4")),
494 ["bgtask", "-t", "log_checker"]
495 );
496 assert_eq!(task.get_trigger(), "log_checker");
497 }
498
499 #[test]
500 fn all_options_injected_in_syntax_order() {
501 let task = BackGroundTask::new("p4", GlobalOpts::new())
502 .retries(3)
503 .detach(true)
504 .interval(86_400)
505 .runs(5)
506 .wait(10)
507 .trigger("p4dstate");
508
509 assert_eq!(
510 args_of(&task.setup_command("p4")),
511 [
512 "bgtask", "-b", "3", "-d", "-i", "86400", "-m", "5", "-w", "10", "-t", "p4dstate"
513 ]
514 );
515 }
516
517 #[test]
518 fn global_opts_are_injected_once() {
519 let task =
520 BackGroundTask::new("p4", GlobalOpts::new().port("localhost:1666")).execute("verify");
521
522 assert_eq!(
523 args_of(&task.setup_command("p4")),
524 ["-p", "localhost:1666", "bgtask", "-e", "verify"]
525 );
526 }
527
528 #[test]
529 fn switching_targets_replaces_selection() {
530 let task = BackGroundTask::new("p4", GlobalOpts::new())
531 .runs(2)
532 .execute("old command")
533 .trigger("verify");
534
535 assert_eq!(
536 args_of(&task.setup_command("p4")),
537 ["bgtask", "-m", "2", "-t", "verify"]
538 );
539 assert_eq!(task.get_trigger(), "verify");
540
541 let task = task.execute("new command");
542 assert_eq!(
543 args_of(&task.setup_command("p4")),
544 ["bgtask", "-m", "2", "-e", "new command"]
545 );
546 assert_eq!(task.get_command(), "new command");
547 }
548
549 #[test]
550 fn interval_accepts_the_document_maximum() {
551 let task = BackGroundTask::new("p4", GlobalOpts::new())
552 .interval(MAX_INTERVAL_SECONDS)
553 .trigger("verify");
554
555 assert_eq!(
556 args_of(&task.setup_command("p4")),
557 ["bgtask", "-i", "2678400", "-t", "verify"]
558 );
559 }
560
561 #[test]
562 #[should_panic(expected = "31 days")]
563 fn interval_above_maximum_panics_in_debug() {
564 let _ = BackGroundTask::new("p4", GlobalOpts::new()).interval(MAX_INTERVAL_SECONDS + 1);
565 }
566
567 #[test]
568 fn setters_update_the_same_command() {
569 let mut task = BackGroundTask::new("p4", GlobalOpts::new()).trigger("verify");
570 task.set_retries(4).set_detach(true).set_wait(30);
571
572 assert_eq!(task.get_retries(), Some(4));
573 assert!(task.get_detach());
574 assert_eq!(task.get_wait(), Some(30));
575 assert_eq!(
576 args_of(&task.setup_command("p4")),
577 ["bgtask", "-b", "4", "-d", "-w", "30", "-t", "verify"]
578 );
579 }
580}