rust/hg-core/src/progress.rs
changeset 52042 92e23ba257d1
parent 52041 3ae7c43ad8aa
equal deleted inserted replaced
52041:3ae7c43ad8aa 52042: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 }