| 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> = self.state.upgrade()?; |
| 637 | let pos: Arc = self.pos.upgrade()?; |
| 638 | let ticker: Arc>> = self.ticker.upgrade()?; |
| 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 | |