comparison rust/hg-core/src/progress.rs @ 52038:92e23ba257d1

rust-hg-cpython: add an `HgProgressBar` util This will be the entry point for all progress bars from a Python context in upcoming patches. Like the `Progress` trait, this is subject to change once we have more use cases, but this is good enough for now.
author Rapha?l Gom?s <rgomes@octobus.net>
date Mon, 30 Sep 2024 16:04:51 +0200
parents 3ae7c43ad8aa
children a876ab6c3fd5
comparison
equal deleted inserted replaced
52037:3ae7c43ad8aa 52038:92e23ba257d1
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 }