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}