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