1 //! Progress-bar related things |
1 //! Progress-bar related things |
|
2 |
|
3 use std::{ |
|
4 sync::atomic::{AtomicBool, Ordering}, |
|
5 time::Duration, |
|
6 }; |
|
7 |
|
8 use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; |
2 |
9 |
3 /// A generic determinate progress bar trait |
10 /// A generic determinate progress bar trait |
4 pub trait Progress: Send + Sync + 'static { |
11 pub trait Progress: Send + Sync + 'static { |
5 /// Set the current position and optionally the total |
12 /// Set the current position and optionally the total |
6 fn update(&self, pos: u64, total: Option<u64>); |
13 fn update(&self, pos: u64, total: Option<u64>); |
7 /// Increment the current position and optionally the total |
14 /// Increment the current position and optionally the total |
8 fn increment(&self, step: u64, total: Option<u64>); |
15 fn increment(&self, step: u64, total: Option<u64>); |
9 /// Declare that progress is over and the progress bar should be deleted |
16 /// Declare that progress is over and the progress bar should be deleted |
10 fn complete(self); |
17 fn complete(self); |
11 } |
18 } |
|
19 |
|
20 const PROGRESS_DELAY: Duration = Duration::from_secs(1); |
|
21 |
|
22 /// A generic (determinate) progress bar. Stays hidden until [`PROGRESS_DELAY`] |
|
23 /// to prevent flickering a progress bar for super fast operations. |
|
24 pub struct HgProgressBar { |
|
25 progress: ProgressBar, |
|
26 has_been_shown: AtomicBool, |
|
27 } |
|
28 |
|
29 impl HgProgressBar { |
|
30 // TODO pass config to check progress.disable/assume-tty/delay/etc. |
|
31 /// Return a new progress bar with `topic` as the prefix. |
|
32 /// The progress and total are both set to 0, and it is hidden until the |
|
33 /// next call to `update` given that more than a second has elapsed. |
|
34 pub fn new(topic: &str) -> Self { |
|
35 let template = |
|
36 format!("{} {{wide_bar}} {{pos}}/{{len}} {{eta}} ", topic); |
|
37 let style = ProgressStyle::with_template(&template).unwrap(); |
|
38 let progress_bar = ProgressBar::new(0).with_style(style); |
|
39 // Hide the progress bar and only show it if we've elapsed more |
|
40 // than a second |
|
41 progress_bar.set_draw_target(ProgressDrawTarget::hidden()); |
|
42 Self { |
|
43 progress: progress_bar, |
|
44 has_been_shown: false.into(), |
|
45 } |
|
46 } |
|
47 |
|
48 /// Called whenever the progress changes to determine whether to start |
|
49 /// showing the progress bar |
|
50 fn maybe_show(&self) { |
|
51 if self.progress.is_hidden() |
|
52 && self.progress.elapsed() > PROGRESS_DELAY |
|
53 { |
|
54 // Catch a race condition whereby we check if it's hidden, then |
|
55 // set the draw target from another thread, then do it again from |
|
56 // this thread, which results in multiple progress bar lines being |
|
57 // left drawn. |
|
58 let has_been_shown = |
|
59 self.has_been_shown.fetch_or(true, Ordering::Relaxed); |
|
60 if !has_been_shown { |
|
61 // Here we are certain that we're the only thread that has |
|
62 // set `has_been_shown` and we can change the draw target |
|
63 self.progress.set_draw_target(ProgressDrawTarget::stderr()); |
|
64 self.progress.tick(); |
|
65 } |
|
66 } |
|
67 } |
|
68 } |
|
69 |
|
70 impl Progress for HgProgressBar { |
|
71 fn update(&self, pos: u64, total: Option<u64>) { |
|
72 self.progress.update(|state| { |
|
73 state.set_pos(pos); |
|
74 if let Some(t) = total { |
|
75 state.set_len(t) |
|
76 } |
|
77 }); |
|
78 self.maybe_show(); |
|
79 } |
|
80 |
|
81 fn increment(&self, step: u64, total: Option<u64>) { |
|
82 self.progress.inc(step); |
|
83 if let Some(t) = total { |
|
84 self.progress.set_length(t) |
|
85 } |
|
86 self.maybe_show(); |
|
87 } |
|
88 |
|
89 fn complete(self) { |
|
90 self.progress.finish_and_clear(); |
|
91 } |
|
92 } |