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
9#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct PurgeAllMode;
21
22#[derive(Debug, Clone, Default, PartialEq, Eq)]
28pub struct FreeSpaceMode {
29 free_space: u64,
30}
31
32#[derive(Debug, Clone, Default, PartialEq, Eq)]
38pub struct RevisionCountMode {
39 revision_count: u64,
40}
41
42#[derive(Debug, Clone, Default, PartialEq, Eq)]
49pub struct SizeMode {
50 size: u64,
51}
52
53impl ExclusiveOption for PurgeAllMode {
58 fn inject_args(&self, command: &mut Command) {
59 command.arg("-a");
60 }
61}
62
63impl ExclusiveOption for FreeSpaceMode {
64 fn inject_args(&self, command: &mut Command) {
65 command.arg("-f").arg(self.free_space.to_string());
66 }
67}
68
69impl ExclusiveOption for RevisionCountMode {
70 fn inject_args(&self, command: &mut Command) {
71 command.arg("-m").arg(self.revision_count.to_string());
72 }
73}
74
75impl ExclusiveOption for SizeMode {
76 fn inject_args(&self, command: &mut Command) {
77 command.arg("-s").arg(self.size.to_string());
78 }
79}
80
81pub trait PurgeGoal: ExclusiveOption {}
88
89impl PurgeGoal for PurgeAllMode {}
90impl PurgeGoal for FreeSpaceMode {}
91impl PurgeGoal for RevisionCountMode {}
92impl PurgeGoal for SizeMode {}
93
94#[derive(Debug, Clone, Default)]
126pub struct CachePurge<M = Unselected> {
127 bin: PathBuf,
128
129 global_opts: GlobalOpts,
130
131 interval: Option<u64>,
133
134 preview: bool,
136
137 filter: Option<String>,
139
140 retain_recent_revision: Option<u64>,
142
143 purge_in_order: bool,
145
146 purge_oldest_first: bool,
148
149 mode: M,
150}
151
152impl CachePurge<Unselected> {
153 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
160 Self {
161 bin: bin.into(),
162 global_opts,
163 ..Default::default()
164 }
165 }
166}
167
168impl<M> CachePurge<M> {
169 pub fn get_bin(&self) -> &Path {
171 &self.bin
172 }
173
174 pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
176 self.bin = bin.into();
177 self
178 }
179
180 pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
182 self.set_bin(bin);
183 self
184 }
185
186 pub fn purge_all(self) -> CachePurge<PurgeAllMode> {
191 CachePurge {
192 bin: self.bin,
193 global_opts: self.global_opts,
194 interval: self.interval,
195 preview: self.preview,
196 filter: self.filter,
197 retain_recent_revision: self.retain_recent_revision,
198 purge_in_order: self.purge_in_order,
199 purge_oldest_first: self.purge_oldest_first,
200 mode: PurgeAllMode,
201 }
202 }
203
204 pub fn free_space(self, n: u64) -> CachePurge<FreeSpaceMode> {
210 CachePurge {
211 bin: self.bin,
212 global_opts: self.global_opts,
213 interval: self.interval,
214 preview: self.preview,
215 filter: self.filter,
216 retain_recent_revision: self.retain_recent_revision,
217 purge_in_order: self.purge_in_order,
218 purge_oldest_first: self.purge_oldest_first,
219 mode: FreeSpaceMode { free_space: n },
220 }
221 }
222
223 pub fn revision_count(self, n: u64) -> CachePurge<RevisionCountMode> {
228 CachePurge {
229 bin: self.bin,
230 global_opts: self.global_opts,
231 interval: self.interval,
232 preview: self.preview,
233 filter: self.filter,
234 retain_recent_revision: self.retain_recent_revision,
235 purge_in_order: self.purge_in_order,
236 purge_oldest_first: self.purge_oldest_first,
237 mode: RevisionCountMode { revision_count: n },
238 }
239 }
240
241 pub fn size(self, n: u64) -> CachePurge<SizeMode> {
246 CachePurge {
247 bin: self.bin,
248 global_opts: self.global_opts,
249 interval: self.interval,
250 preview: self.preview,
251 filter: self.filter,
252 retain_recent_revision: self.retain_recent_revision,
253 purge_in_order: self.purge_in_order,
254 purge_oldest_first: self.purge_oldest_first,
255 mode: SizeMode { size: n },
256 }
257 }
258
259 #[cfg_attr(
264 all(feature = "lt2018_2", not(feature = "lt2017_1")),
265 doc = "See [Global Options](GlobalOpts)."
266 )]
267 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
268 pub fn get_global_opts(&self) -> &GlobalOpts {
269 &self.global_opts
270 }
271
272 #[cfg_attr(
277 all(feature = "lt2018_2", not(feature = "lt2017_1")),
278 doc = "See [Global Options](GlobalOpts)."
279 )]
280 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
281 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
282 self.global_opts = v;
283 self
284 }
285
286 #[cfg_attr(
291 all(feature = "lt2018_2", not(feature = "lt2017_1")),
292 doc = "See [Global Options](GlobalOpts)."
293 )]
294 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
295 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
296 self.global_opts = v;
297 self
298 }
299
300 pub fn get_interval(&self) -> Option<u64> {
307 self.interval
308 }
309
310 pub fn set_interval(&mut self, v: u64) -> &mut Self {
317 self.interval = Some(v);
318 self
319 }
320
321 pub fn interval(mut self, v: u64) -> Self {
328 self.interval = Some(v);
329 self
330 }
331
332 pub fn get_preview(&self) -> bool {
339 self.preview
340 }
341
342 pub fn set_preview(&mut self, v: bool) -> &mut Self {
349 self.preview = v;
350 self
351 }
352
353 pub fn preview(mut self, v: bool) -> Self {
360 self.preview = v;
361 self
362 }
363
364 pub fn get_filter(&self) -> Option<&str> {
371 self.filter.as_deref()
372 }
373
374 pub fn set_filter(&mut self, v: impl Into<String>) -> &mut Self {
381 self.filter = Some(v.into());
382 self
383 }
384
385 pub fn filter(mut self, v: impl Into<String>) -> Self {
392 self.filter = Some(v.into());
393 self
394 }
395
396 pub fn get_retain_recent_revision(&self) -> Option<u64> {
404 self.retain_recent_revision
405 }
406
407 pub fn set_retain_recent_revision(&mut self, v: u64) -> &mut Self {
415 self.retain_recent_revision = Some(v);
416 self
417 }
418
419 pub fn retain_recent_revision(mut self, v: u64) -> Self {
427 self.retain_recent_revision = Some(v);
428 self
429 }
430 pub fn get_purge_in_order(&self) -> bool {
438 self.purge_in_order
439 }
440
441 pub fn set_purge_in_order(&mut self, v: bool) -> &mut Self {
449 self.purge_in_order = v;
450 self
451 }
452
453 pub fn purge_in_order(mut self, v: bool) -> Self {
461 self.purge_in_order = v;
462 self
463 }
464
465 pub fn get_purge_oldest_first(&self) -> bool {
472 self.purge_oldest_first
473 }
474
475 pub fn set_purge_oldest_first(&mut self, v: bool) -> &mut Self {
482 self.purge_oldest_first = v;
483 self
484 }
485
486 pub fn purge_oldest_first(mut self, v: bool) -> Self {
493 self.purge_oldest_first = v;
494 self
495 }
496}
497
498impl CachePurge<FreeSpaceMode> {
499 pub fn get_free_space(&self) -> u64 {
501 self.mode.free_space
502 }
503}
504
505impl CachePurge<RevisionCountMode> {
506 pub fn get_revision_count(&self) -> u64 {
508 self.mode.revision_count
509 }
510}
511
512impl CachePurge<SizeMode> {
513 pub fn get_size(&self) -> u64 {
515 self.mode.size
516 }
517}
518
519impl<M: PurgeGoal> SubCommand for CachePurge<M> {
524 fn name(&self) -> &str {
525 "cachepurge"
526 }
527
528 fn inject_local_args(&self, command: &mut Command) {
529 self.mode.inject_args(command);
530
531 if let Some(interval) = self.interval {
532 command.arg("-i").arg(interval.to_string());
533 }
534
535 if self.preview {
536 command.arg("-n");
537 }
538
539 if self.purge_in_order {
540 command.arg("-R");
541 }
542
543 if let Some(retain) = self.retain_recent_revision {
544 command.arg("-S").arg(retain.to_string());
545 }
546
547 if self.purge_oldest_first {
548 command.arg("-O");
549 }
550
551 if let Some(filter) = &self.filter {
552 command.arg("-D").arg(filter);
553 }
554 }
555
556 fn global_opts(&self) -> Option<&GlobalOpts> {
557 Some(&self.global_opts)
558 }
559}
560
561impl<M: PurgeGoal> ParameterizedSpawn<()> for CachePurge<M> {
566 type Output = Child;
567 type Error = std::io::Error;
568
569 fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
573 self.setup_command(&self.bin)
574 .stdout(Stdio::piped())
575 .stderr(Stdio::piped())
576 .spawn()
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583 use crate::cmd::args_of;
584
585 #[test]
586 fn bin_accessors_update_executable_path() {
587 let mut command = CachePurge::new("p4", GlobalOpts::new()).purge_all();
588 assert_eq!(command.get_bin(), Path::new("p4"));
589
590 command.set_bin("/usr/local/bin/p4");
591 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
592
593 let command = command.bin("p4.exe");
594 assert_eq!(command.get_bin(), Path::new("p4.exe"));
595 }
596
597 #[test]
600 fn purge_all_goal_only() {
601 let purge = CachePurge::new("p4", GlobalOpts::new()).purge_all();
602
603 assert_eq!(args_of(&purge.setup_command("p4")), ["cachepurge", "-a"]);
604 }
605
606 #[test]
607 fn free_space_goal_only() {
608 let purge = CachePurge::new("p4", GlobalOpts::new()).free_space(1_073_741_824);
609
610 assert_eq!(
611 args_of(&purge.setup_command("p4")),
612 ["cachepurge", "-f", "1073741824"]
613 );
614 assert_eq!(purge.get_free_space(), 1_073_741_824);
615 }
616
617 #[test]
618 fn revision_count_goal_only() {
619 let purge = CachePurge::new("p4", GlobalOpts::new()).revision_count(100);
620
621 assert_eq!(
622 args_of(&purge.setup_command("p4")),
623 ["cachepurge", "-m", "100"]
624 );
625 assert_eq!(purge.get_revision_count(), 100);
626 }
627
628 #[test]
629 fn size_goal_only() {
630 let purge = CachePurge::new("p4", GlobalOpts::new()).size(2_048);
631
632 assert_eq!(
633 args_of(&purge.setup_command("p4")),
634 ["cachepurge", "-s", "2048"]
635 );
636 assert_eq!(purge.get_size(), 2_048);
637 }
638
639 #[test]
640 fn order_flags_are_injected() {
641 let purge = CachePurge::new("p4", GlobalOpts::new())
642 .purge_all()
643 .purge_in_order(true);
644
645 assert_eq!(
646 args_of(&purge.setup_command("p4")),
647 ["cachepurge", "-a", "-R"]
648 );
649 assert!(purge.get_purge_in_order());
650 assert!(!purge.get_purge_oldest_first());
651
652 let purge = purge.purge_oldest_first(true);
653
654 assert_eq!(
655 args_of(&purge.setup_command("p4")),
656 ["cachepurge", "-a", "-R", "-O"]
657 );
658 assert!(purge.get_purge_in_order());
659 assert!(purge.get_purge_oldest_first());
660 }
661
662 #[test]
663 fn order_flags_available_in_every_goal_state() {
664 let purge = CachePurge::new("p4", GlobalOpts::new())
665 .free_space(1_024)
666 .purge_in_order(true)
667 .purge_oldest_first(true);
668
669 assert_eq!(
670 args_of(&purge.setup_command("p4")),
671 ["cachepurge", "-f", "1024", "-R", "-O"]
672 );
673
674 let purge = purge.revision_count(10);
675 assert_eq!(
676 args_of(&purge.setup_command("p4")),
677 ["cachepurge", "-m", "10", "-R", "-O"]
678 );
679
680 let purge = purge.size(2_048);
681 assert_eq!(
682 args_of(&purge.setup_command("p4")),
683 ["cachepurge", "-s", "2048", "-R", "-O"]
684 );
685 }
686
687 #[test]
688 fn all_options_injected_in_syntax_order() {
689 let purge = CachePurge::new("p4", GlobalOpts::new())
690 .purge_all()
691 .purge_in_order(true)
692 .purge_oldest_first(true)
693 .interval(86_400)
694 .preview(true)
695 .retain_recent_revision(1)
696 .filter("//depot/...");
697
698 assert_eq!(
699 args_of(&purge.setup_command("p4")),
700 [
701 "cachepurge",
702 "-a",
703 "-i",
704 "86400",
705 "-n",
706 "-R",
707 "-S",
708 "1",
709 "-O",
710 "-D",
711 "//depot/..."
712 ]
713 );
714 }
715
716 #[test]
717 fn switching_goals_preserves_common_options() {
718 let purge = CachePurge::new("p4", GlobalOpts::new())
719 .interval(60)
720 .purge_all()
721 .purge_oldest_first(true)
722 .size(4_096);
723
724 assert_eq!(
725 args_of(&purge.setup_command("p4")),
726 ["cachepurge", "-s", "4096", "-i", "60", "-O"]
727 );
728 assert_eq!(purge.get_size(), 4_096);
729 assert!(purge.get_purge_oldest_first());
730
731 let purge = purge.free_space(1_024);
732
733 assert_eq!(
734 args_of(&purge.setup_command("p4")),
735 ["cachepurge", "-f", "1024", "-i", "60", "-O"]
736 );
737 assert_eq!(purge.get_free_space(), 1_024);
738 }
739
740 #[test]
741 fn global_opts_are_injected_once() {
742 let purge =
743 CachePurge::new("p4", GlobalOpts::new().port("localhost:1666")).revision_count(10);
744
745 assert_eq!(
746 args_of(&purge.setup_command("p4")),
747 ["-p", "localhost:1666", "cachepurge", "-m", "10"]
748 );
749 }
750
751 #[test]
752 fn setters_update_the_same_command() {
753 let mut purge = CachePurge::new("p4", GlobalOpts::new()).free_space(2_048);
754 purge
755 .set_preview(true)
756 .set_purge_in_order(true)
757 .set_purge_oldest_first(true)
758 .set_retain_recent_revision(3)
759 .set_filter("//depot/src/...")
760 .set_interval(300);
761
762 assert!(purge.get_preview());
763 assert!(purge.get_purge_in_order());
764 assert!(purge.get_purge_oldest_first());
765 assert_eq!(purge.get_retain_recent_revision(), Some(3));
766 assert_eq!(purge.get_filter(), Some("//depot/src/..."));
767 assert_eq!(purge.get_interval(), Some(300));
768 assert_eq!(
769 args_of(&purge.setup_command("p4")),
770 [
771 "cachepurge",
772 "-f",
773 "2048",
774 "-i",
775 "300",
776 "-n",
777 "-R",
778 "-S",
779 "3",
780 "-O",
781 "-D",
782 "//depot/src/..."
783 ]
784 );
785 }
786}