Skip to main content

indicatif/
progress_bar.rs

1#[cfg(test)]
2use portable_atomic::{AtomicBool, Ordering};
3use std::borrow::Cow;
4use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
5use std::time::Duration;
6#[cfg(not(target_arch = "wasm32"))]
7use std::time::Instant;
8use std::{fmt, io, thread};
9
10#[cfg(test)]
11use once_cell::sync::Lazy;
12#[cfg(all(target_arch = "wasm32", feature = "wasmbind"))]
13use web_time::Instant;
14
15use crate::draw_target::ProgressDrawTarget;
16use crate::state::{AtomicPosition, BarState, ProgressFinish, Reset, TabExpandedString};
17use crate::style::ProgressStyle;
18use crate::{iter, ProgressBarIter, ProgressIterator, ProgressState};
19
20/// A progress bar or spinner
21///
22/// The progress bar is an [`Arc`] around its internal state. When the progress bar is cloned it
23/// just increments the refcount (so the original and its clone share the same state).
24#[derive(Clone)]
25pub struct ProgressBar {
26    state: Arc<Mutex<BarState>>,
27    pos: Arc<AtomicPosition>,
28    ticker: Arc<Mutex<Option<Ticker>>>,
29}
30
31impl fmt::Debug for ProgressBar {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        f.debug_struct("ProgressBar").finish()
34    }
35}
36
37impl ProgressBar {
38    /// Creates a new progress bar with a given length
39    ///
40    /// This progress bar by default draws directly to stderr, and refreshes a maximum of 20 times
41    /// a second. To change the refresh rate, [set] the [draw target] to one with a different refresh
42    /// rate.
43    ///
44    /// [set]: ProgressBar::set_draw_target
45    /// [draw target]: ProgressDrawTarget
46    pub fn new(len: u64) -> Self {
47        Self::with_draw_target(Some(len), ProgressDrawTarget::stderr())
48    }
49
50    /// Creates a new progress bar without a specified length
51    ///
52    /// This progress bar by default draws directly to stderr, and refreshes a maximum of 20 times
53    /// a second. To change the refresh rate, [set] the [draw target] to one with a different refresh
54    /// rate.
55    ///
56    /// [set]: ProgressBar::set_draw_target
57    /// [draw target]: ProgressDrawTarget
58    pub fn no_length() -> Self {
59        Self::with_draw_target(None, ProgressDrawTarget::stderr())
60    }
61
62    /// Creates a completely hidden progress bar
63    ///
64    /// This progress bar still responds to API changes but it does not have a length or render in
65    /// any way.
66    pub fn hidden() -> Self {
67        Self::with_draw_target(None, ProgressDrawTarget::hidden())
68    }
69
70    /// Creates a new progress bar with a given length and draw target
71    pub fn with_draw_target(len: Option<u64>, draw_target: ProgressDrawTarget) -> Self {
72        let pos = Arc::new(AtomicPosition::new());
73        Self {
74            state: Arc::new(Mutex::new(BarState::new(len, draw_target, pos.clone()))),
75            pos,
76            ticker: Arc::new(Mutex::new(None)),
77        }
78    }
79
80    /// Get a clone of the current progress bar style.
81    pub fn style(&self) -> ProgressStyle {
82        self.state().style.clone()
83    }
84
85    /// A convenience builder-like function for a progress bar with a given style
86    pub fn with_style(self, style: ProgressStyle) -> Self {
87        self.set_style(style);
88        self
89    }
90
91    /// A convenience builder-like function for a progress bar with a given tab width
92    pub fn with_tab_width(self, tab_width: usize) -> Self {
93        self.state().set_tab_width(tab_width);
94        self
95    }
96
97    /// A convenience builder-like function for a progress bar with a given prefix
98    ///
99    /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template
100    /// (see [`ProgressStyle`]).
101    pub fn with_prefix(self, prefix: impl Into<Cow<'static, str>>) -> Self {
102        let mut state = self.state();
103        state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
104        drop(state);
105        self
106    }
107
108    /// A convenience builder-like function for a progress bar with a given message
109    ///
110    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
111    /// [`ProgressStyle`]).
112    pub fn with_message(self, message: impl Into<Cow<'static, str>>) -> Self {
113        let mut state = self.state();
114        state.state.message = TabExpandedString::new(message.into(), state.tab_width);
115        drop(state);
116        self
117    }
118
119    /// A convenience builder-like function for a progress bar with a given position
120    pub fn with_position(self, pos: u64) -> Self {
121        self.state().state.set_pos(pos);
122        self
123    }
124
125    /// A convenience builder-like function for a progress bar with a given elapsed time
126    pub fn with_elapsed(self, elapsed: Duration) -> Self {
127        self.state().state.started = Instant::now().checked_sub(elapsed).unwrap();
128        self
129    }
130
131    /// Sets the finish behavior for the progress bar
132    ///
133    /// This behavior is invoked when [`ProgressBar`] or
134    /// [`ProgressBarIter`] completes and
135    /// [`ProgressBar::is_finished()`] is false.
136    /// If you don't want the progress bar to be automatically finished then
137    /// call `with_finish(Abandon)`.
138    ///
139    /// [`ProgressBar`]: crate::ProgressBar
140    /// [`ProgressBarIter`]: crate::ProgressBarIter
141    /// [`ProgressBar::is_finished()`]: crate::ProgressBar::is_finished
142    pub fn with_finish(self, finish: ProgressFinish) -> Self {
143        self.state().on_finish = finish;
144        self
145    }
146
147    /// Creates a new spinner
148    ///
149    /// This spinner by default draws directly to stderr. This adds the default spinner style to it.
150    pub fn new_spinner() -> Self {
151        let rv = Self::with_draw_target(None, ProgressDrawTarget::stderr());
152        rv.set_style(ProgressStyle::default_spinner());
153        rv
154    }
155
156    /// Overrides the stored style
157    ///
158    /// This does not redraw the bar. Call [`ProgressBar::tick()`] to force it.
159    pub fn set_style(&self, mut style: ProgressStyle) {
160        let mut state = self.state();
161        if state.draw_target.is_stderr() {
162            style.set_for_stderr()
163        };
164        state.set_style(style);
165    }
166
167    /// Sets the tab width (default: 8). All tabs will be expanded to this many spaces.
168    pub fn set_tab_width(&self, tab_width: usize) {
169        let mut state = self.state();
170        state.set_tab_width(tab_width);
171        state.draw(true, Instant::now()).unwrap();
172    }
173
174    /// Spawns a background thread to tick the progress bar
175    ///
176    /// When this is enabled a background thread will regularly tick the progress bar in the given
177    /// interval. This is useful to advance progress bars that are very slow by themselves.
178    ///
179    /// When steady ticks are enabled, calling [`ProgressBar::tick()`] on a progress bar does not
180    /// have any effect.
181    pub fn enable_steady_tick(&self, interval: Duration) {
182        // The way we test for ticker termination is with a single static `AtomicBool`. Since cargo
183        // runs tests concurrently, we have a `TICKER_TEST` lock to make sure tests using ticker
184        // don't step on each other. This check catches attempts to use tickers in tests without
185        // acquiring the lock.
186        #[cfg(test)]
187        {
188            let guard = TICKER_TEST.try_lock();
189            let lock_acquired = guard.is_ok();
190            // Drop the guard before panicking to avoid poisoning the lock (which would cause other
191            // ticker tests to fail)
192            drop(guard);
193            if lock_acquired {
194                panic!("you must acquire the TICKER_TEST lock in your test to use this method");
195            }
196        }
197
198        if interval.is_zero() {
199            return;
200        }
201
202        self.stop_and_replace_ticker(Some(interval));
203    }
204
205    /// Undoes [`ProgressBar::enable_steady_tick()`]
206    pub fn disable_steady_tick(&self) {
207        self.stop_and_replace_ticker(None);
208    }
209
210    fn stop_and_replace_ticker(&self, interval: Option<Duration>) {
211        let mut ticker_state = self.ticker.lock().unwrap();
212        if let Some(ticker) = ticker_state.take() {
213            ticker.stop();
214        }
215
216        *ticker_state = interval.map(|interval| Ticker::new(interval, &self.state));
217    }
218
219    /// Manually ticks the spinner or progress bar
220    ///
221    /// This automatically happens on any other change to a progress bar.
222    pub fn tick(&self) {
223        self.tick_inner(Instant::now());
224    }
225
226    fn tick_inner(&self, now: Instant) {
227        // Only tick if a `Ticker` isn't installed
228        if self.ticker.lock().unwrap().is_none() {
229            self.state().tick(now);
230        }
231    }
232
233    /// Advances the position of the progress bar by `delta`
234    pub fn inc(&self, delta: u64) {
235        self.pos.inc(delta);
236        let now = Instant::now();
237        if self.pos.allow(now) {
238            self.tick_inner(now);
239        }
240    }
241
242    /// Decrease the position of the progress bar by `delta`
243    pub fn dec(&self, delta: u64) {
244        self.pos.dec(delta);
245        let now = Instant::now();
246        if self.pos.allow(now) {
247            self.tick_inner(now);
248        }
249    }
250
251    /// A quick convenience check if the progress bar is hidden
252    pub fn is_hidden(&self) -> bool {
253        self.state().draw_target.is_hidden()
254    }
255
256    /// Indicates that the progress bar finished
257    pub fn is_finished(&self) -> bool {
258        self.state().state.is_finished()
259    }
260
261    /// Print a log line above the progress bar
262    ///
263    /// If the progress bar is hidden (e.g. when standard output is not a terminal), `println()`
264    /// will not do anything. If you want to write to the standard output in such cases as well, use
265    /// [`ProgressBar::suspend()`] instead.
266    ///
267    /// If the progress bar was added to a [`MultiProgress`], the log line will be
268    /// printed above all other progress bars.
269    ///
270    /// [`ProgressBar::suspend()`]: ProgressBar::suspend
271    /// [`MultiProgress`]: crate::MultiProgress
272    pub fn println<I: AsRef<str>>(&self, msg: I) {
273        self.state().println(Instant::now(), msg.as_ref());
274    }
275
276    /// Update the `ProgressBar`'s inner [`ProgressState`]
277    pub fn update(&self, f: impl FnOnce(&mut ProgressState)) {
278        self.state()
279            .update(Instant::now(), f, self.ticker.lock().unwrap().is_none());
280    }
281
282    /// Sets the position of the progress bar
283    pub fn set_position(&self, pos: u64) {
284        self.pos.set(pos);
285        let now = Instant::now();
286        if self.pos.allow(now) {
287            self.tick_inner(now);
288        }
289    }
290
291    /// Sets the length of the progress bar to `None`
292    pub fn unset_length(&self) {
293        self.state().unset_length(Instant::now());
294    }
295
296    /// Sets the length of the progress bar
297    pub fn set_length(&self, len: u64) {
298        self.state().set_length(Instant::now(), len);
299    }
300
301    /// Increase the length of the progress bar
302    pub fn inc_length(&self, delta: u64) {
303        self.state().inc_length(Instant::now(), delta);
304    }
305
306    /// Decrease the length of the progress bar
307    pub fn dec_length(&self, delta: u64) {
308        self.state().dec_length(Instant::now(), delta);
309    }
310
311    /// Sets the current prefix of the progress bar
312    ///
313    /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template
314    /// (see [`ProgressStyle`]).
315    pub fn set_prefix(&self, prefix: impl Into<Cow<'static, str>>) {
316        let mut state = self.state();
317        state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
318        state.update_estimate_and_draw(Instant::now());
319    }
320
321    /// Sets the current message of the progress bar
322    ///
323    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
324    /// [`ProgressStyle`]).
325    pub fn set_message(&self, msg: impl Into<Cow<'static, str>>) {
326        let mut state = self.state();
327        state.state.message = TabExpandedString::new(msg.into(), state.tab_width);
328        state.update_estimate_and_draw(Instant::now());
329    }
330
331    /// Sets the elapsed time for the progress bar
332    pub fn set_elapsed(&self, elapsed: Duration) {
333        self.state().state.started = Instant::now().checked_sub(elapsed).unwrap();
334    }
335
336    /// Creates a new weak reference to this [`ProgressBar`]
337    pub fn downgrade(&self) -> WeakProgressBar {
338        WeakProgressBar {
339            state: Arc::downgrade(&self.state),
340            pos: Arc::downgrade(&self.pos),
341            ticker: Arc::downgrade(&self.ticker),
342        }
343    }
344
345    /// Resets the ETA calculation
346    ///
347    /// This can be useful if the progress bars made a large jump or was paused for a prolonged
348    /// time.
349    pub fn reset_eta(&self) {
350        self.state().reset(Instant::now(), Reset::Eta);
351    }
352
353    /// Resets elapsed time and the ETA calculation
354    pub fn reset_elapsed(&self) {
355        self.state().reset(Instant::now(), Reset::Elapsed);
356    }
357
358    /// Resets all of the progress bar state
359    pub fn reset(&self) {
360        self.state().reset(Instant::now(), Reset::All);
361    }
362
363    /// Finishes the progress bar and leaves the current message
364    pub fn finish(&self) {
365        self.state()
366            .finish_using_style(Instant::now(), ProgressFinish::AndLeave);
367    }
368
369    /// Finishes the progress bar and sets a message
370    ///
371    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
372    /// [`ProgressStyle`]).
373    pub fn finish_with_message(&self, msg: impl Into<Cow<'static, str>>) {
374        self.state()
375            .finish_using_style(Instant::now(), ProgressFinish::WithMessage(msg.into()));
376    }
377
378    /// Finishes the progress bar and completely clears it
379    pub fn finish_and_clear(&self) {
380        self.state()
381            .finish_using_style(Instant::now(), ProgressFinish::AndClear);
382    }
383
384    /// Finishes the progress bar and leaves the current message and progress
385    pub fn abandon(&self) {
386        self.state()
387            .finish_using_style(Instant::now(), ProgressFinish::Abandon);
388    }
389
390    /// Finishes the progress bar and sets a message, and leaves the current progress
391    ///
392    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
393    /// [`ProgressStyle`]).
394    pub fn abandon_with_message(&self, msg: impl Into<Cow<'static, str>>) {
395        self.state().finish_using_style(
396            Instant::now(),
397            ProgressFinish::AbandonWithMessage(msg.into()),
398        );
399    }
400
401    /// Finishes the progress bar using the behavior stored in the [`ProgressStyle`]
402    ///
403    /// See [`ProgressBar::with_finish()`].
404    pub fn finish_using_style(&self) {
405        let mut state = self.state();
406        let finish = state.on_finish.clone();
407        state.finish_using_style(Instant::now(), finish);
408    }
409
410    /// Sets a different draw target for the progress bar
411    ///
412    /// This can be used to draw the progress bar to stderr (this is the default):
413    ///
414    /// ```rust,no_run
415    /// # use indicatif::{ProgressBar, ProgressDrawTarget};
416    /// let pb = ProgressBar::new(100);
417    /// pb.set_draw_target(ProgressDrawTarget::stderr());
418    /// ```
419    ///
420    /// **Note:** Calling this method on a [`ProgressBar`] linked with a [`MultiProgress`] (after
421    /// running [`MultiProgress::add()`]) will unlink this progress bar. If you don't want this
422    /// behavior, call [`MultiProgress::set_draw_target()`] instead.
423    ///
424    /// Use [`ProgressBar::with_draw_target()`] to set the draw target during creation.
425    ///
426    /// [`MultiProgress`]: crate::MultiProgress
427    /// [`MultiProgress::add()`]: crate::MultiProgress::add
428    /// [`MultiProgress::set_draw_target()`]: crate::MultiProgress::set_draw_target
429    pub fn set_draw_target(&self, target: ProgressDrawTarget) {
430        let mut state = self.state();
431        state.draw_target.disconnect(Instant::now());
432        state.draw_target = target;
433    }
434
435    /// Force a redraw of the progress bar to be in sync with its state
436    ///
437    /// For performance reasons the progress bar is not redrawn on each state update.
438    /// This is normally not an issue, since new updates will eventually trigger rendering.
439    ///
440    /// For slow running tasks it is recommended to rely on [`ProgressBar::enable_steady_tick()`]
441    /// to ensure continued rendering of the progress bar.
442    pub fn force_draw(&self) {
443        let _ = self.state().draw(true, Instant::now());
444    }
445
446    /// Hide the progress bar temporarily, execute `f`, then redraw the progress bar
447    ///
448    /// Useful for external code that writes to the standard output.
449    ///
450    /// If the progress bar was added to a [`MultiProgress`], it will suspend the entire [`MultiProgress`].
451    ///
452    /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print
453    /// anything on the progress bar will be blocked until `f` finishes.
454    /// Therefore, it is recommended to avoid long-running operations in `f`.
455    ///
456    /// ```rust,no_run
457    /// # use indicatif::ProgressBar;
458    /// let mut pb = ProgressBar::new(3);
459    /// pb.suspend(|| {
460    ///     println!("Log message");
461    /// })
462    /// ```
463    ///
464    /// [`MultiProgress`]: crate::MultiProgress
465    pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R {
466        self.state().suspend(Instant::now(), f)
467    }
468
469    /// Wraps an [`Iterator`] with the progress bar
470    ///
471    /// ```rust,no_run
472    /// # use indicatif::ProgressBar;
473    /// let v = vec![1, 2, 3];
474    /// let pb = ProgressBar::new(3);
475    /// for item in pb.wrap_iter(v.iter()) {
476    ///     // ...
477    /// }
478    /// ```
479    pub fn wrap_iter<It: Iterator>(&self, it: It) -> ProgressBarIter<It> {
480        it.progress_with(self.clone())
481    }
482
483    /// Wraps an [`io::Read`] with the progress bar
484    ///
485    /// ```rust,no_run
486    /// # use std::fs::File;
487    /// # use std::io;
488    /// # use indicatif::ProgressBar;
489    /// # fn test () -> io::Result<()> {
490    /// let source = File::open("work.txt")?;
491    /// let mut target = File::create("done.txt")?;
492    /// let pb = ProgressBar::new(source.metadata()?.len());
493    /// io::copy(&mut pb.wrap_read(source), &mut target);
494    /// # Ok(())
495    /// # }
496    /// ```
497    pub fn wrap_read<R: io::Read>(&self, read: R) -> ProgressBarIter<R> {
498        ProgressBarIter {
499            progress: self.clone(),
500            it: read,
501            seek_max: iter::SeekMax::default(),
502        }
503    }
504
505    /// Wraps an [`io::Write`] with the progress bar
506    ///
507    /// ```rust,no_run
508    /// # use std::fs::File;
509    /// # use std::io;
510    /// # use indicatif::ProgressBar;
511    /// # fn test () -> io::Result<()> {
512    /// let mut source = File::open("work.txt")?;
513    /// let target = File::create("done.txt")?;
514    /// let pb = ProgressBar::new(source.metadata()?.len());
515    /// io::copy(&mut source, &mut pb.wrap_write(target));
516    /// # Ok(())
517    /// # }
518    /// ```
519    pub fn wrap_write<W: io::Write>(&self, write: W) -> ProgressBarIter<W> {
520        ProgressBarIter {
521            progress: self.clone(),
522            it: write,
523            seek_max: iter::SeekMax::default(),
524        }
525    }
526
527    #[cfg(feature = "tokio")]
528    #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
529    /// Wraps an [`tokio::io::AsyncWrite`] with the progress bar
530    ///
531    /// ```rust,no_run
532    /// # use tokio::fs::File;
533    /// # use tokio::io;
534    /// # use indicatif::ProgressBar;
535    /// # async fn test() -> io::Result<()> {
536    /// let mut source = File::open("work.txt").await?;
537    /// let mut target = File::open("done.txt").await?;
538    /// let pb = ProgressBar::new(source.metadata().await?.len());
539    /// io::copy(&mut source, &mut pb.wrap_async_write(target)).await?;
540    /// # Ok(())
541    /// # }
542    /// ```
543    pub fn wrap_async_write<W: tokio::io::AsyncWrite + Unpin>(
544        &self,
545        write: W,
546    ) -> ProgressBarIter<W> {
547        ProgressBarIter {
548            progress: self.clone(),
549            it: write,
550            seek_max: iter::SeekMax::default(),
551        }
552    }
553
554    #[cfg(feature = "tokio")]
555    #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
556    /// Wraps an [`tokio::io::AsyncRead`] with the progress bar
557    ///
558    /// ```rust,no_run
559    /// # use tokio::fs::File;
560    /// # use tokio::io;
561    /// # use indicatif::ProgressBar;
562    /// # async fn test() -> io::Result<()> {
563    /// let mut source = File::open("work.txt").await?;
564    /// let mut target = File::open("done.txt").await?;
565    /// let pb = ProgressBar::new(source.metadata().await?.len());
566    /// io::copy(&mut pb.wrap_async_read(source), &mut target).await?;
567    /// # Ok(())
568    /// # }
569    /// ```
570    pub fn wrap_async_read<R: tokio::io::AsyncRead + Unpin>(&self, read: R) -> ProgressBarIter<R> {
571        ProgressBarIter {
572            progress: self.clone(),
573            it: read,
574            seek_max: iter::SeekMax::default(),
575        }
576    }
577
578    /// Wraps a [`futures::Stream`](https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html) with the progress bar
579    ///
580    /// ```
581    /// # use indicatif::ProgressBar;
582    /// # futures::executor::block_on(async {
583    /// use futures::stream::{self, StreamExt};
584    /// let pb = ProgressBar::new(10);
585    /// let mut stream = pb.wrap_stream(stream::iter('a'..='z'));
586    ///
587    /// assert_eq!(stream.next().await, Some('a'));
588    /// assert_eq!(stream.count().await, 25);
589    /// # }); // block_on
590    /// ```
591    #[cfg(feature = "futures")]
592    #[cfg_attr(docsrs, doc(cfg(feature = "futures")))]
593    pub fn wrap_stream<S: futures_core::Stream>(&self, stream: S) -> ProgressBarIter<S> {
594        ProgressBarIter {
595            progress: self.clone(),
596            it: stream,
597            seek_max: iter::SeekMax::default(),
598        }
599    }
600
601    /// Returns the current position
602    pub fn position(&self) -> u64 {
603        self.state().state.pos()
604    }
605
606    /// Returns the current length
607    pub fn length(&self) -> Option<u64> {
608        self.state().state.len()
609    }
610
611    /// Returns the current ETA
612    pub fn eta(&self) -> Duration {
613        self.state().state.eta()
614    }
615
616    /// Returns the current rate of progress
617    pub fn per_sec(&self) -> f64 {
618        self.state().state.per_sec()
619    }
620
621    /// Returns the current expected duration
622    pub fn duration(&self) -> Duration {
623        self.state().state.duration()
624    }
625
626    /// Returns the current elapsed time
627    pub fn elapsed(&self) -> Duration {
628        self.state().state.elapsed()
629    }
630
631    /// Returns the current tab width
632    pub fn tab_width(&self) -> usize {
633        self.state().tab_width
634    }
635
636    /// Index in the `MultiState`
637    pub(crate) fn index(&self) -> Option<usize> {
638        self.state().draw_target.remote().map(|(_, idx)| idx)
639    }
640
641    /// Current message
642    pub fn message(&self) -> String {
643        self.state().state.message.expanded().to_string()
644    }
645
646    /// Current prefix
647    pub fn prefix(&self) -> String {
648        self.state().state.prefix.expanded().to_string()
649    }
650
651    #[inline]
652    pub(crate) fn state(&self) -> MutexGuard<'_, BarState> {
653        self.state.lock().unwrap()
654    }
655}
656
657/// A weak reference to a [`ProgressBar`].
658///
659/// Useful for creating custom steady tick implementations
660#[derive(Clone, Default)]
661pub struct WeakProgressBar {
662    state: Weak<Mutex<BarState>>,
663    pos: Weak<AtomicPosition>,
664    ticker: Weak<Mutex<Option<Ticker>>>,
665}
666
667impl WeakProgressBar {
668    /// Create a new [`WeakProgressBar`] that returns `None` when [`upgrade()`] is called.
669    ///
670    /// [`upgrade()`]: WeakProgressBar::upgrade
671    pub fn new() -> Self {
672        Self::default()
673    }
674
675    /// Attempts to upgrade the Weak pointer to a [`ProgressBar`], delaying dropping of the inner
676    /// value if successful. Returns [`None`] if the inner value has since been dropped.
677    ///
678    /// [`ProgressBar`]: struct.ProgressBar.html
679    pub fn upgrade(&self) -> Option<ProgressBar> {
680        let state = self.state.upgrade()?;
681        let pos = self.pos.upgrade()?;
682        let ticker = self.ticker.upgrade()?;
683        Some(ProgressBar { state, pos, ticker })
684    }
685}
686
687pub(crate) struct Ticker {
688    stopping: Arc<(Mutex<bool>, Condvar)>,
689    join_handle: Option<thread::JoinHandle<()>>,
690}
691
692impl Drop for Ticker {
693    fn drop(&mut self) {
694        self.stop();
695        self.join_handle.take().map(|handle| handle.join());
696    }
697}
698
699#[cfg(test)]
700static TICKER_RUNNING: AtomicBool = AtomicBool::new(false);
701
702impl Ticker {
703    pub(crate) fn new(interval: Duration, bar_state: &Arc<Mutex<BarState>>) -> Self {
704        debug_assert!(!interval.is_zero());
705
706        // A `Mutex<bool>` is used as a flag to indicate whether the ticker was requested to stop.
707        // The `Condvar` is used a notification mechanism: when the ticker is dropped, we notify
708        // the thread and interrupt the ticker wait.
709        #[allow(clippy::mutex_atomic)]
710        let stopping = Arc::new((Mutex::new(false), Condvar::new()));
711        let control = TickerControl {
712            stopping: stopping.clone(),
713            state: Arc::downgrade(bar_state),
714        };
715
716        let join_handle = thread::spawn(move || control.run(interval));
717        Self {
718            stopping,
719            join_handle: Some(join_handle),
720        }
721    }
722
723    pub(crate) fn stop(&self) {
724        *self.stopping.0.lock().unwrap() = true;
725        self.stopping.1.notify_one();
726    }
727}
728
729struct TickerControl {
730    stopping: Arc<(Mutex<bool>, Condvar)>,
731    state: Weak<Mutex<BarState>>,
732}
733
734impl TickerControl {
735    fn run(&self, interval: Duration) {
736        #[cfg(test)]
737        TICKER_RUNNING.store(true, Ordering::SeqCst);
738
739        while let Some(arc) = self.state.upgrade() {
740            let mut state = arc.lock().unwrap();
741            if state.state.is_finished() {
742                break;
743            }
744
745            state.tick(Instant::now());
746
747            drop(state); // Don't forget to drop the lock before sleeping
748            drop(arc); // Also need to drop Arc otherwise BarState won't be dropped
749
750            // Wait for `interval` but return early if we are notified to stop
751            let result = self
752                .stopping
753                .1
754                .wait_timeout_while(self.stopping.0.lock().unwrap(), interval, |stopped| {
755                    !*stopped
756                })
757                .unwrap();
758
759            // If the wait didn't time out, it means we were notified to stop
760            if !result.1.timed_out() {
761                break;
762            }
763        }
764
765        #[cfg(test)]
766        TICKER_RUNNING.store(false, Ordering::SeqCst);
767    }
768}
769
770// Tests using the global TICKER_RUNNING flag need to be serialized
771#[cfg(test)]
772pub(crate) static TICKER_TEST: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
773
774#[cfg(test)]
775mod tests {
776    use super::*;
777
778    #[allow(clippy::float_cmp)]
779    #[test]
780    fn test_pbar_zero() {
781        let pb = ProgressBar::new(0);
782        assert_eq!(pb.state().state.fraction(), 1.0);
783    }
784
785    #[allow(clippy::float_cmp)]
786    #[test]
787    fn test_pbar_maxu64() {
788        let pb = ProgressBar::new(!0);
789        assert_eq!(pb.state().state.fraction(), 0.0);
790    }
791
792    #[test]
793    fn test_pbar_overflow() {
794        let pb = ProgressBar::new(1);
795        pb.set_draw_target(ProgressDrawTarget::hidden());
796        pb.inc(2);
797        pb.finish();
798    }
799
800    #[test]
801    fn test_get_position() {
802        let pb = ProgressBar::new(1);
803        pb.set_draw_target(ProgressDrawTarget::hidden());
804        pb.inc(2);
805        let pos = pb.position();
806        assert_eq!(pos, 2);
807    }
808
809    #[test]
810    fn test_weak_pb() {
811        let pb = ProgressBar::new(0);
812        let weak = pb.downgrade();
813        assert!(weak.upgrade().is_some());
814        ::std::mem::drop(pb);
815        assert!(weak.upgrade().is_none());
816    }
817
818    #[test]
819    fn it_can_wrap_a_reader() {
820        let bytes = &b"I am an implementation of io::Read"[..];
821        let pb = ProgressBar::new(bytes.len() as u64);
822        let mut reader = pb.wrap_read(bytes);
823        let mut writer = Vec::new();
824        io::copy(&mut reader, &mut writer).unwrap();
825        assert_eq!(writer, bytes);
826    }
827
828    #[test]
829    fn it_can_wrap_a_writer() {
830        let bytes = b"implementation of io::Read";
831        let mut reader = &bytes[..];
832        let pb = ProgressBar::new(bytes.len() as u64);
833        let writer = Vec::new();
834        let mut writer = pb.wrap_write(writer);
835        io::copy(&mut reader, &mut writer).unwrap();
836        assert_eq!(writer.it, bytes);
837    }
838
839    #[test]
840    fn ticker_thread_terminates_on_drop() {
841        let _guard = TICKER_TEST.lock().unwrap();
842        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
843
844        let pb = ProgressBar::new_spinner();
845        pb.enable_steady_tick(Duration::from_millis(50));
846
847        // Give the thread time to start up
848        thread::sleep(Duration::from_millis(250));
849
850        assert!(TICKER_RUNNING.load(Ordering::SeqCst));
851
852        drop(pb);
853        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
854    }
855
856    #[test]
857    fn ticker_thread_terminates_on_drop_2() {
858        let _guard = TICKER_TEST.lock().unwrap();
859        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
860
861        let pb = ProgressBar::new_spinner();
862        pb.enable_steady_tick(Duration::from_millis(50));
863        let pb2 = pb.clone();
864
865        // Give the thread time to start up
866        thread::sleep(Duration::from_millis(250));
867
868        assert!(TICKER_RUNNING.load(Ordering::SeqCst));
869
870        drop(pb);
871        assert!(TICKER_RUNNING.load(Ordering::SeqCst));
872
873        drop(pb2);
874        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
875    }
876}