Skip to main content

compare_dir/
progress.rs

1use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
2use std::path::Path;
3use std::time::Duration;
4
5const SPINNER_STYLE: &str = "{elapsed_precise} {pos:>7} {spinner:.green} {msg}";
6const NORMAL_STYLE: &str =
7    "{elapsed_precise} +{eta:>3} {percent:>3}% {pos:>7}/{len:7} {bar:40.cyan/blue} {msg}";
8const FILE_STYLE: &str = "  {elapsed:>3} +{eta:>3} {percent:>3}% {msg}";
9
10#[derive(Debug, Clone)]
11pub(crate) struct Progress {
12    inner: Option<ProgressBar>,
13    multi: Option<MultiProgress>,
14}
15
16impl Progress {
17    pub fn none() -> Self {
18        Self {
19            inner: None,
20            multi: None,
21        }
22    }
23
24    pub fn set_message(&self, msg: impl Into<String>) {
25        if let Some(inner) = &self.inner {
26            inner.set_message(msg.into());
27        }
28    }
29
30    pub fn inc(&self, amount: u64) {
31        if let Some(inner) = &self.inner {
32            inner.inc(amount);
33        }
34    }
35
36    pub fn length(&self) -> Option<u64> {
37        self.inner.as_ref().and_then(|inner| inner.length())
38    }
39
40    pub fn set_length(&self, len: u64) {
41        if let Some(inner) = &self.inner {
42            if len > 0 && inner.length().is_none() {
43                inner.set_style(ProgressStyle::with_template(NORMAL_STYLE).unwrap());
44            }
45            inner.set_length(len);
46        }
47    }
48
49    pub fn finish(&self) {
50        if let Some(inner) = &self.inner {
51            inner.finish();
52            if let Some(multi) = &self.multi {
53                multi.remove(inner);
54            }
55        }
56    }
57
58    pub fn suspend<F, R>(&self, f: F) -> R
59    where
60        F: FnOnce() -> R,
61    {
62        if let Some(multi) = &self.multi {
63            multi.suspend(f)
64        } else if let Some(inner) = &self.inner {
65            inner.suspend(f)
66        } else {
67            f()
68        }
69    }
70}
71
72#[derive(Debug)]
73pub struct ProgressBuilder {
74    multi: MultiProgress,
75    pub is_file_enabled: bool,
76}
77
78impl ProgressBuilder {
79    pub fn new() -> Self {
80        Self {
81            multi: MultiProgress::new(),
82            is_file_enabled: false,
83        }
84    }
85
86    pub(crate) fn add_spinner(&self) -> Progress {
87        let progress = self.multi.add(ProgressBar::new_spinner());
88        progress.enable_steady_tick(Duration::from_millis(120));
89        progress.set_style(ProgressStyle::with_template(SPINNER_STYLE).unwrap());
90        Progress {
91            inner: Some(progress),
92            multi: Some(self.multi.clone()),
93        }
94    }
95
96    pub(crate) fn add_file(&self, path: &Path, file_size: u64) -> Progress {
97        if !self.is_file_enabled {
98            return Progress::none();
99        }
100        let progress = self.multi.add(ProgressBar::new(file_size));
101        progress.set_style(ProgressStyle::with_template(FILE_STYLE).unwrap());
102        if let Some(parent) = path.parent()
103            && let Some(file_name) = path.file_name()
104        {
105            progress.set_message(format!(
106                "{} ({})",
107                file_name.to_string_lossy(),
108                parent.to_string_lossy()
109            ));
110        } else {
111            progress.set_message(path.to_string_lossy().to_string());
112        }
113        Progress {
114            inner: Some(progress),
115            multi: Some(self.multi.clone()),
116        }
117    }
118}
119
120impl Default for ProgressBuilder {
121    fn default() -> Self {
122        Self::new()
123    }
124}