summaryrefslogtreecommitdiff
path: root/vendor/indicatif/src/progress_bar.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
committerValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
commit1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch)
tree7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/indicatif/src/progress_bar.rs
parent5ecd8cf2cba827454317368b68571df0d13d7842 (diff)
downloadfparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz
fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/indicatif/src/progress_bar.rs')
-rw-r--r--vendor/indicatif/src/progress_bar.rs808
1 files changed, 808 insertions, 0 deletions
diff --git a/vendor/indicatif/src/progress_bar.rs b/vendor/indicatif/src/progress_bar.rs
new file mode 100644
index 0000000..938668e
--- /dev/null
+++ b/vendor/indicatif/src/progress_bar.rs
@@ -0,0 +1,808 @@
+#[cfg(test)]
+use portable_atomic::{AtomicBool, Ordering};
+use std::borrow::Cow;
+use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
+use std::time::Duration;
+#[cfg(not(target_arch = "wasm32"))]
+use std::time::Instant;
+use std::{fmt, io, thread};
+
+#[cfg(target_arch = "wasm32")]
+use instant::Instant;
+#[cfg(test)]
+use once_cell::sync::Lazy;
+
+use crate::draw_target::ProgressDrawTarget;
+use crate::state::{AtomicPosition, BarState, ProgressFinish, Reset, TabExpandedString};
+use crate::style::ProgressStyle;
+use crate::{ProgressBarIter, ProgressIterator, ProgressState};
+
+/// A progress bar or spinner
+///
+/// The progress bar is an [`Arc`] around its internal state. When the progress bar is cloned it
+/// just increments the refcount (so the original and its clone share the same state).
+#[derive(Clone)]
+pub struct ProgressBar {
+ state: Arc<Mutex<BarState>>,
+ pos: Arc<AtomicPosition>,
+ ticker: Arc<Mutex<Option<Ticker>>>,
+}
+
+impl fmt::Debug for ProgressBar {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("ProgressBar").finish()
+ }
+}
+
+impl ProgressBar {
+ /// Creates a new progress bar with a given length
+ ///
+ /// This progress bar by default draws directly to stderr, and refreshes a maximum of 15 times
+ /// a second. To change the refresh rate, set the draw target to one with a different refresh
+ /// rate.
+ pub fn new(len: u64) -> Self {
+ Self::with_draw_target(Some(len), ProgressDrawTarget::stderr())
+ }
+
+ /// Creates a completely hidden progress bar
+ ///
+ /// This progress bar still responds to API changes but it does not have a length or render in
+ /// any way.
+ pub fn hidden() -> Self {
+ Self::with_draw_target(None, ProgressDrawTarget::hidden())
+ }
+
+ /// Creates a new progress bar with a given length and draw target
+ pub fn with_draw_target(len: Option<u64>, draw_target: ProgressDrawTarget) -> Self {
+ let pos = Arc::new(AtomicPosition::new());
+ Self {
+ state: Arc::new(Mutex::new(BarState::new(len, draw_target, pos.clone()))),
+ pos,
+ ticker: Arc::new(Mutex::new(None)),
+ }
+ }
+
+ /// Get a clone of the current progress bar style.
+ pub fn style(&self) -> ProgressStyle {
+ self.state().style.clone()
+ }
+
+ /// A convenience builder-like function for a progress bar with a given style
+ pub fn with_style(self, style: ProgressStyle) -> Self {
+ self.set_style(style);
+ self
+ }
+
+ /// A convenience builder-like function for a progress bar with a given tab width
+ pub fn with_tab_width(self, tab_width: usize) -> Self {
+ self.state().set_tab_width(tab_width);
+ self
+ }
+
+ /// A convenience builder-like function for a progress bar with a given prefix
+ ///
+ /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template
+ /// (see [`ProgressStyle`]).
+ pub fn with_prefix(self, prefix: impl Into<Cow<'static, str>>) -> Self {
+ let mut state = self.state();
+ state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
+ drop(state);
+ self
+ }
+
+ /// A convenience builder-like function for a progress bar with a given message
+ ///
+ /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
+ /// [`ProgressStyle`]).
+ pub fn with_message(self, message: impl Into<Cow<'static, str>>) -> Self {
+ let mut state = self.state();
+ state.state.message = TabExpandedString::new(message.into(), state.tab_width);
+ drop(state);
+ self
+ }
+
+ /// A convenience builder-like function for a progress bar with a given position
+ pub fn with_position(self, pos: u64) -> Self {
+ self.state().state.set_pos(pos);
+ self
+ }
+
+ /// A convenience builder-like function for a progress bar with a given elapsed time
+ pub fn with_elapsed(self, elapsed: Duration) -> Self {
+ self.state().state.started = Instant::now().checked_sub(elapsed).unwrap();
+ self
+ }
+
+ /// Sets the finish behavior for the progress bar
+ ///
+ /// This behavior is invoked when [`ProgressBar`] or
+ /// [`ProgressBarIter`] completes and
+ /// [`ProgressBar::is_finished()`] is false.
+ /// If you don't want the progress bar to be automatically finished then
+ /// call `on_finish(None)`.
+ ///
+ /// [`ProgressBar`]: crate::ProgressBar
+ /// [`ProgressBarIter`]: crate::ProgressBarIter
+ /// [`ProgressBar::is_finished()`]: crate::ProgressBar::is_finished
+ pub fn with_finish(self, finish: ProgressFinish) -> Self {
+ self.state().on_finish = finish;
+ self
+ }
+
+ /// Creates a new spinner
+ ///
+ /// This spinner by default draws directly to stderr. This adds the default spinner style to it.
+ pub fn new_spinner() -> Self {
+ let rv = Self::with_draw_target(None, ProgressDrawTarget::stderr());
+ rv.set_style(ProgressStyle::default_spinner());
+ rv
+ }
+
+ /// Overrides the stored style
+ ///
+ /// This does not redraw the bar. Call [`ProgressBar::tick()`] to force it.
+ pub fn set_style(&self, style: ProgressStyle) {
+ self.state().set_style(style);
+ }
+
+ /// Sets the tab width (default: 8). All tabs will be expanded to this many spaces.
+ pub fn set_tab_width(&mut self, tab_width: usize) {
+ let mut state = self.state();
+ state.set_tab_width(tab_width);
+ state.draw(true, Instant::now()).unwrap();
+ }
+
+ /// Spawns a background thread to tick the progress bar
+ ///
+ /// When this is enabled a background thread will regularly tick the progress bar in the given
+ /// interval. This is useful to advance progress bars that are very slow by themselves.
+ ///
+ /// When steady ticks are enabled, calling [`ProgressBar::tick()`] on a progress bar does not
+ /// have any effect.
+ pub fn enable_steady_tick(&self, interval: Duration) {
+ // The way we test for ticker termination is with a single static `AtomicBool`. Since cargo
+ // runs tests concurrently, we have a `TICKER_TEST` lock to make sure tests using ticker
+ // don't step on each other. This check catches attempts to use tickers in tests without
+ // acquiring the lock.
+ #[cfg(test)]
+ {
+ let guard = TICKER_TEST.try_lock();
+ let lock_acquired = guard.is_ok();
+ // Drop the guard before panicking to avoid poisoning the lock (which would cause other
+ // ticker tests to fail)
+ drop(guard);
+ if lock_acquired {
+ panic!("you must acquire the TICKER_TEST lock in your test to use this method");
+ }
+ }
+
+ if interval.is_zero() {
+ return;
+ }
+
+ self.stop_and_replace_ticker(Some(interval));
+ }
+
+ /// Undoes [`ProgressBar::enable_steady_tick()`]
+ pub fn disable_steady_tick(&self) {
+ self.stop_and_replace_ticker(None);
+ }
+
+ fn stop_and_replace_ticker(&self, interval: Option<Duration>) {
+ let mut ticker_state = self.ticker.lock().unwrap();
+ if let Some(ticker) = ticker_state.take() {
+ ticker.stop();
+ }
+
+ *ticker_state = interval.map(|interval| Ticker::new(interval, &self.state));
+ }
+
+ /// Manually ticks the spinner or progress bar
+ ///
+ /// This automatically happens on any other change to a progress bar.
+ pub fn tick(&self) {
+ self.tick_inner(Instant::now());
+ }
+
+ fn tick_inner(&self, now: Instant) {
+ // Only tick if a `Ticker` isn't installed
+ if self.ticker.lock().unwrap().is_none() {
+ self.state().tick(now);
+ }
+ }
+
+ /// Advances the position of the progress bar by `delta`
+ pub fn inc(&self, delta: u64) {
+ self.pos.inc(delta);
+ let now = Instant::now();
+ if self.pos.allow(now) {
+ self.tick_inner(now);
+ }
+ }
+
+ /// A quick convenience check if the progress bar is hidden
+ pub fn is_hidden(&self) -> bool {
+ self.state().draw_target.is_hidden()
+ }
+
+ /// Indicates that the progress bar finished
+ pub fn is_finished(&self) -> bool {
+ self.state().state.is_finished()
+ }
+
+ /// Print a log line above the progress bar
+ ///
+ /// If the progress bar is hidden (e.g. when standard output is not a terminal), `println()`
+ /// will not do anything. If you want to write to the standard output in such cases as well, use
+ /// [`suspend`] instead.
+ ///
+ /// If the progress bar was added to a [`MultiProgress`], the log line will be
+ /// printed above all other progress bars.
+ ///
+ /// [`suspend`]: ProgressBar::suspend
+ /// [`MultiProgress`]: crate::MultiProgress
+ pub fn println<I: AsRef<str>>(&self, msg: I) {
+ self.state().println(Instant::now(), msg.as_ref());
+ }
+
+ /// Update the `ProgressBar`'s inner [`ProgressState`]
+ pub fn update(&self, f: impl FnOnce(&mut ProgressState)) {
+ self.state()
+ .update(Instant::now(), f, self.ticker.lock().unwrap().is_none());
+ }
+
+ /// Sets the position of the progress bar
+ pub fn set_position(&self, pos: u64) {
+ self.pos.set(pos);
+ let now = Instant::now();
+ if self.pos.allow(now) {
+ self.tick_inner(now);
+ }
+ }
+
+ /// Sets the length of the progress bar
+ pub fn set_length(&self, len: u64) {
+ self.state().set_length(Instant::now(), len);
+ }
+
+ /// Increase the length of the progress bar
+ pub fn inc_length(&self, delta: u64) {
+ self.state().inc_length(Instant::now(), delta);
+ }
+
+ /// Sets the current prefix of the progress bar
+ ///
+ /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template
+ /// (see [`ProgressStyle`]).
+ pub fn set_prefix(&self, prefix: impl Into<Cow<'static, str>>) {
+ let mut state = self.state();
+ state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
+ state.update_estimate_and_draw(Instant::now());
+ }
+
+ /// Sets the current message of the progress bar
+ ///
+ /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
+ /// [`ProgressStyle`]).
+ pub fn set_message(&self, msg: impl Into<Cow<'static, str>>) {
+ let mut state = self.state();
+ state.state.message = TabExpandedString::new(msg.into(), state.tab_width);
+ state.update_estimate_and_draw(Instant::now());
+ }
+
+ /// Creates a new weak reference to this `ProgressBar`
+ pub fn downgrade(&self) -> WeakProgressBar {
+ WeakProgressBar {
+ state: Arc::downgrade(&self.state),
+ pos: Arc::downgrade(&self.pos),
+ ticker: Arc::downgrade(&self.ticker),
+ }
+ }
+
+ /// Resets the ETA calculation
+ ///
+ /// This can be useful if the progress bars made a large jump or was paused for a prolonged
+ /// time.
+ pub fn reset_eta(&self) {
+ self.state().reset(Instant::now(), Reset::Eta);
+ }
+
+ /// Resets elapsed time and the ETA calculation
+ pub fn reset_elapsed(&self) {
+ self.state().reset(Instant::now(), Reset::Elapsed);
+ }
+
+ /// Resets all of the progress bar state
+ pub fn reset(&self) {
+ self.state().reset(Instant::now(), Reset::All);
+ }
+
+ /// Finishes the progress bar and leaves the current message
+ pub fn finish(&self) {
+ self.state()
+ .finish_using_style(Instant::now(), ProgressFinish::AndLeave);
+ }
+
+ /// Finishes the progress bar and sets a message
+ ///
+ /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
+ /// [`ProgressStyle`]).
+ pub fn finish_with_message(&self, msg: impl Into<Cow<'static, str>>) {
+ self.state()
+ .finish_using_style(Instant::now(), ProgressFinish::WithMessage(msg.into()));
+ }
+
+ /// Finishes the progress bar and completely clears it
+ pub fn finish_and_clear(&self) {
+ self.state()
+ .finish_using_style(Instant::now(), ProgressFinish::AndClear);
+ }
+
+ /// Finishes the progress bar and leaves the current message and progress
+ pub fn abandon(&self) {
+ self.state()
+ .finish_using_style(Instant::now(), ProgressFinish::Abandon);
+ }
+
+ /// Finishes the progress bar and sets a message, and leaves the current progress
+ ///
+ /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
+ /// [`ProgressStyle`]).
+ pub fn abandon_with_message(&self, msg: impl Into<Cow<'static, str>>) {
+ self.state().finish_using_style(
+ Instant::now(),
+ ProgressFinish::AbandonWithMessage(msg.into()),
+ );
+ }
+
+ /// Finishes the progress bar using the behavior stored in the [`ProgressStyle`]
+ ///
+ /// See [`ProgressBar::with_finish()`].
+ pub fn finish_using_style(&self) {
+ let mut state = self.state();
+ let finish = state.on_finish.clone();
+ state.finish_using_style(Instant::now(), finish);
+ }
+
+ /// Sets a different draw target for the progress bar
+ ///
+ /// This can be used to draw the progress bar to stderr (this is the default):
+ ///
+ /// ```rust,no_run
+ /// # use indicatif::{ProgressBar, ProgressDrawTarget};
+ /// let pb = ProgressBar::new(100);
+ /// pb.set_draw_target(ProgressDrawTarget::stderr());
+ /// ```
+ ///
+ /// **Note:** Calling this method on a [`ProgressBar`] linked with a [`MultiProgress`] (after
+ /// running [`MultiProgress::add`]) will unlink this progress bar. If you don't want this
+ /// behavior, call [`MultiProgress::set_draw_target`] instead.
+ ///
+ /// [`MultiProgress`]: crate::MultiProgress
+ /// [`MultiProgress::add`]: crate::MultiProgress::add
+ /// [`MultiProgress::set_draw_target`]: crate::MultiProgress::set_draw_target
+ pub fn set_draw_target(&self, target: ProgressDrawTarget) {
+ let mut state = self.state();
+ state.draw_target.disconnect(Instant::now());
+ state.draw_target = target;
+ }
+
+ /// Hide the progress bar temporarily, execute `f`, then redraw the progress bar
+ ///
+ /// Useful for external code that writes to the standard output.
+ ///
+ /// If the progress bar was added to a MultiProgress, it will suspend the entire MultiProgress
+ ///
+ /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print
+ /// anything on the progress bar will be blocked until `f` finishes.
+ /// Therefore, it is recommended to avoid long-running operations in `f`.
+ ///
+ /// ```rust,no_run
+ /// # use indicatif::ProgressBar;
+ /// let mut pb = ProgressBar::new(3);
+ /// pb.suspend(|| {
+ /// println!("Log message");
+ /// })
+ /// ```
+ pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R {
+ self.state().suspend(Instant::now(), f)
+ }
+
+ /// Wraps an [`Iterator`] with the progress bar
+ ///
+ /// ```rust,no_run
+ /// # use indicatif::ProgressBar;
+ /// let v = vec![1, 2, 3];
+ /// let pb = ProgressBar::new(3);
+ /// for item in pb.wrap_iter(v.iter()) {
+ /// // ...
+ /// }
+ /// ```
+ pub fn wrap_iter<It: Iterator>(&self, it: It) -> ProgressBarIter<It> {
+ it.progress_with(self.clone())
+ }
+
+ /// Wraps an [`io::Read`] with the progress bar
+ ///
+ /// ```rust,no_run
+ /// # use std::fs::File;
+ /// # use std::io;
+ /// # use indicatif::ProgressBar;
+ /// # fn test () -> io::Result<()> {
+ /// let source = File::open("work.txt")?;
+ /// let mut target = File::create("done.txt")?;
+ /// let pb = ProgressBar::new(source.metadata()?.len());
+ /// io::copy(&mut pb.wrap_read(source), &mut target);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn wrap_read<R: io::Read>(&self, read: R) -> ProgressBarIter<R> {
+ ProgressBarIter {
+ progress: self.clone(),
+ it: read,
+ }
+ }
+
+ /// Wraps an [`io::Write`] with the progress bar
+ ///
+ /// ```rust,no_run
+ /// # use std::fs::File;
+ /// # use std::io;
+ /// # use indicatif::ProgressBar;
+ /// # fn test () -> io::Result<()> {
+ /// let mut source = File::open("work.txt")?;
+ /// let target = File::create("done.txt")?;
+ /// let pb = ProgressBar::new(source.metadata()?.len());
+ /// io::copy(&mut source, &mut pb.wrap_write(target));
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn wrap_write<W: io::Write>(&self, write: W) -> ProgressBarIter<W> {
+ ProgressBarIter {
+ progress: self.clone(),
+ it: write,
+ }
+ }
+
+ #[cfg(feature = "tokio")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
+ /// Wraps an [`tokio::io::AsyncWrite`] with the progress bar
+ ///
+ /// ```rust,no_run
+ /// # use tokio::fs::File;
+ /// # use tokio::io;
+ /// # use indicatif::ProgressBar;
+ /// # async fn test() -> io::Result<()> {
+ /// let mut source = File::open("work.txt").await?;
+ /// let mut target = File::open("done.txt").await?;
+ /// let pb = ProgressBar::new(source.metadata().await?.len());
+ /// io::copy(&mut source, &mut pb.wrap_async_write(target)).await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn wrap_async_write<W: tokio::io::AsyncWrite + Unpin>(
+ &self,
+ write: W,
+ ) -> ProgressBarIter<W> {
+ ProgressBarIter {
+ progress: self.clone(),
+ it: write,
+ }
+ }
+
+ #[cfg(feature = "tokio")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
+ /// Wraps an [`tokio::io::AsyncRead`] with the progress bar
+ ///
+ /// ```rust,no_run
+ /// # use tokio::fs::File;
+ /// # use tokio::io;
+ /// # use indicatif::ProgressBar;
+ /// # async fn test() -> io::Result<()> {
+ /// let mut source = File::open("work.txt").await?;
+ /// let mut target = File::open("done.txt").await?;
+ /// let pb = ProgressBar::new(source.metadata().await?.len());
+ /// io::copy(&mut pb.wrap_async_read(source), &mut target).await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn wrap_async_read<R: tokio::io::AsyncRead + Unpin>(&self, read: R) -> ProgressBarIter<R> {
+ ProgressBarIter {
+ progress: self.clone(),
+ it: read,
+ }
+ }
+
+ /// Wraps a [`futures::Stream`](https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html) with the progress bar
+ ///
+ /// ```
+ /// # use indicatif::ProgressBar;
+ /// # futures::executor::block_on(async {
+ /// use futures::stream::{self, StreamExt};
+ /// let pb = ProgressBar::new(10);
+ /// let mut stream = pb.wrap_stream(stream::iter('a'..='z'));
+ ///
+ /// assert_eq!(stream.next().await, Some('a'));
+ /// assert_eq!(stream.count().await, 25);
+ /// # }); // block_on
+ /// ```
+ #[cfg(feature = "futures")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "futures")))]
+ pub fn wrap_stream<S: futures_core::Stream>(&self, stream: S) -> ProgressBarIter<S> {
+ ProgressBarIter {
+ progress: self.clone(),
+ it: stream,
+ }
+ }
+
+ /// Returns the current position
+ pub fn position(&self) -> u64 {
+ self.state().state.pos()
+ }
+
+ /// Returns the current length
+ pub fn length(&self) -> Option<u64> {
+ self.state().state.len()
+ }
+
+ /// Returns the current ETA
+ pub fn eta(&self) -> Duration {
+ self.state().state.eta()
+ }
+
+ /// Returns the current rate of progress
+ pub fn per_sec(&self) -> f64 {
+ self.state().state.per_sec()
+ }
+
+ /// Returns the current expected duration
+ pub fn duration(&self) -> Duration {
+ self.state().state.duration()
+ }
+
+ /// Returns the current elapsed time
+ pub fn elapsed(&self) -> Duration {
+ self.state().state.elapsed()
+ }
+
+ /// Index in the `MultiState`
+ pub(crate) fn index(&self) -> Option<usize> {
+ self.state().draw_target.remote().map(|(_, idx)| idx)
+ }
+
+ /// Current message
+ pub fn message(&self) -> String {
+ self.state().state.message.expanded().to_string()
+ }
+
+ /// Current prefix
+ pub fn prefix(&self) -> String {
+ self.state().state.prefix.expanded().to_string()
+ }
+
+ #[inline]
+ pub(crate) fn state(&self) -> MutexGuard<'_, BarState> {
+ self.state.lock().unwrap()
+ }
+}
+
+/// A weak reference to a `ProgressBar`.
+///
+/// Useful for creating custom steady tick implementations
+#[derive(Clone, Default)]
+pub struct WeakProgressBar {
+ state: Weak<Mutex<BarState>>,
+ pos: Weak<AtomicPosition>,
+ ticker: Weak<Mutex<Option<Ticker>>>,
+}
+
+impl WeakProgressBar {
+ /// Create a new `WeakProgressBar` that returns `None` when [`upgrade`] is called.
+ ///
+ /// [`upgrade`]: WeakProgressBar::upgrade
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Attempts to upgrade the Weak pointer to a [`ProgressBar`], delaying dropping of the inner
+ /// value if successful. Returns `None` if the inner value has since been dropped.
+ ///
+ /// [`ProgressBar`]: struct.ProgressBar.html
+ pub fn upgrade(&self) -> Option<ProgressBar> {
+ let state = self.state.upgrade()?;
+ let pos = self.pos.upgrade()?;
+ let ticker = self.ticker.upgrade()?;
+ Some(ProgressBar { state, pos, ticker })
+ }
+}
+
+pub(crate) struct Ticker {
+ stopping: Arc<(Mutex<bool>, Condvar)>,
+ join_handle: Option<thread::JoinHandle<()>>,
+}
+
+impl Drop for Ticker {
+ fn drop(&mut self) {
+ self.stop();
+ self.join_handle.take().map(|handle| handle.join());
+ }
+}
+
+#[cfg(test)]
+static TICKER_RUNNING: AtomicBool = AtomicBool::new(false);
+
+impl Ticker {
+ pub(crate) fn new(interval: Duration, bar_state: &Arc<Mutex<BarState>>) -> Self {
+ debug_assert!(!interval.is_zero());
+
+ // A `Mutex<bool>` is used as a flag to indicate whether the ticker was requested to stop.
+ // The `Condvar` is used a notification mechanism: when the ticker is dropped, we notify
+ // the thread and interrupt the ticker wait.
+ #[allow(clippy::mutex_atomic)]
+ let stopping = Arc::new((Mutex::new(false), Condvar::new()));
+ let control = TickerControl {
+ stopping: stopping.clone(),
+ state: Arc::downgrade(bar_state),
+ };
+
+ let join_handle = thread::spawn(move || control.run(interval));
+ Self {
+ stopping,
+ join_handle: Some(join_handle),
+ }
+ }
+
+ pub(crate) fn stop(&self) {
+ *self.stopping.0.lock().unwrap() = true;
+ self.stopping.1.notify_one();
+ }
+}
+
+struct TickerControl {
+ stopping: Arc<(Mutex<bool>, Condvar)>,
+ state: Weak<Mutex<BarState>>,
+}
+
+impl TickerControl {
+ fn run(&self, interval: Duration) {
+ #[cfg(test)]
+ TICKER_RUNNING.store(true, Ordering::SeqCst);
+
+ while let Some(arc) = self.state.upgrade() {
+ let mut state = arc.lock().unwrap();
+ if state.state.is_finished() {
+ break;
+ }
+
+ state.tick(Instant::now());
+
+ drop(state); // Don't forget to drop the lock before sleeping
+ drop(arc); // Also need to drop Arc otherwise BarState won't be dropped
+
+ // Wait for `interval` but return early if we are notified to stop
+ let (_, result) = self
+ .stopping
+ .1
+ .wait_timeout_while(self.stopping.0.lock().unwrap(), interval, |stopped| {
+ !*stopped
+ })
+ .unwrap();
+
+ // If the wait didn't time out, it means we were notified to stop
+ if !result.timed_out() {
+ break;
+ }
+ }
+
+ #[cfg(test)]
+ TICKER_RUNNING.store(false, Ordering::SeqCst);
+ }
+}
+
+// Tests using the global TICKER_RUNNING flag need to be serialized
+#[cfg(test)]
+pub(crate) static TICKER_TEST: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[allow(clippy::float_cmp)]
+ #[test]
+ fn test_pbar_zero() {
+ let pb = ProgressBar::new(0);
+ assert_eq!(pb.state().state.fraction(), 1.0);
+ }
+
+ #[allow(clippy::float_cmp)]
+ #[test]
+ fn test_pbar_maxu64() {
+ let pb = ProgressBar::new(!0);
+ assert_eq!(pb.state().state.fraction(), 0.0);
+ }
+
+ #[test]
+ fn test_pbar_overflow() {
+ let pb = ProgressBar::new(1);
+ pb.set_draw_target(ProgressDrawTarget::hidden());
+ pb.inc(2);
+ pb.finish();
+ }
+
+ #[test]
+ fn test_get_position() {
+ let pb = ProgressBar::new(1);
+ pb.set_draw_target(ProgressDrawTarget::hidden());
+ pb.inc(2);
+ let pos = pb.position();
+ assert_eq!(pos, 2);
+ }
+
+ #[test]
+ fn test_weak_pb() {
+ let pb = ProgressBar::new(0);
+ let weak = pb.downgrade();
+ assert!(weak.upgrade().is_some());
+ ::std::mem::drop(pb);
+ assert!(weak.upgrade().is_none());
+ }
+
+ #[test]
+ fn it_can_wrap_a_reader() {
+ let bytes = &b"I am an implementation of io::Read"[..];
+ let pb = ProgressBar::new(bytes.len() as u64);
+ let mut reader = pb.wrap_read(bytes);
+ let mut writer = Vec::new();
+ io::copy(&mut reader, &mut writer).unwrap();
+ assert_eq!(writer, bytes);
+ }
+
+ #[test]
+ fn it_can_wrap_a_writer() {
+ let bytes = b"implementation of io::Read";
+ let mut reader = &bytes[..];
+ let pb = ProgressBar::new(bytes.len() as u64);
+ let writer = Vec::new();
+ let mut writer = pb.wrap_write(writer);
+ io::copy(&mut reader, &mut writer).unwrap();
+ assert_eq!(writer.it, bytes);
+ }
+
+ #[test]
+ fn ticker_thread_terminates_on_drop() {
+ let _guard = TICKER_TEST.lock().unwrap();
+ assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
+
+ let pb = ProgressBar::new_spinner();
+ pb.enable_steady_tick(Duration::from_millis(50));
+
+ // Give the thread time to start up
+ thread::sleep(Duration::from_millis(250));
+
+ assert!(TICKER_RUNNING.load(Ordering::SeqCst));
+
+ drop(pb);
+ assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
+ }
+
+ #[test]
+ fn ticker_thread_terminates_on_drop_2() {
+ let _guard = TICKER_TEST.lock().unwrap();
+ assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
+
+ let pb = ProgressBar::new_spinner();
+ pb.enable_steady_tick(Duration::from_millis(50));
+ let pb2 = pb.clone();
+
+ // Give the thread time to start up
+ thread::sleep(Duration::from_millis(250));
+
+ assert!(TICKER_RUNNING.load(Ordering::SeqCst));
+
+ drop(pb);
+ assert!(TICKER_RUNNING.load(Ordering::SeqCst));
+
+ drop(pb2);
+ assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
+ }
+}