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