1 use std::borrow::Cow; 2 use std::fmt; 3 use std::io; 4 use std::sync::atomic::{AtomicBool, Ordering}; 5 use std::sync::mpsc::{channel, Receiver, Sender}; 6 use std::sync::{Arc, Mutex, RwLock, Weak}; 7 use std::thread; 8 use std::time::{Duration, Instant}; 9 10 use crate::state::{ 11 MultiObject, MultiProgressState, ProgressDrawState, ProgressDrawTarget, ProgressDrawTargetKind, 12 ProgressState, Status, 13 }; 14 use crate::style::ProgressStyle; 15 use crate::utils::Estimate; 16 use crate::{ProgressBarIter, ProgressIterator}; 17 18 /// A progress bar or spinner 19 /// 20 /// The progress bar is an [`Arc`] around its internal state. When the progress bar is cloned it 21 /// just increments the refcount (so the original and its clone share the same state). 22 #[derive(Clone)] 23 pub struct ProgressBar { 24 state: Arc<Mutex<ProgressState>>, 25 } 26 27 impl fmt::Debug for ProgressBar { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 29 f.debug_struct("ProgressBar").finish() 30 } 31 } 32 33 impl ProgressBar { 34 /// Creates a new progress bar with a given length 35 /// 36 /// This progress bar by default draws directly to stderr, and refreshes a maximum of 15 times 37 /// a second. To change the refresh rate, set the draw target to one with a different refresh 38 /// rate. new(len: u64) -> ProgressBar39 pub fn new(len: u64) -> ProgressBar { 40 ProgressBar::with_draw_target(len, ProgressDrawTarget::stderr()) 41 } 42 43 /// Creates a completely hidden progress bar 44 /// 45 /// This progress bar still responds to API changes but it does not have a length or render in 46 /// any way. hidden() -> ProgressBar47 pub fn hidden() -> ProgressBar { 48 ProgressBar::with_draw_target(!0, ProgressDrawTarget::hidden()) 49 } 50 51 /// Creates a new progress bar with a given length and draw target with_draw_target(len: u64, target: ProgressDrawTarget) -> ProgressBar52 pub fn with_draw_target(len: u64, target: ProgressDrawTarget) -> ProgressBar { 53 ProgressBar { 54 state: Arc::new(Mutex::new(ProgressState { 55 style: ProgressStyle::default_bar(), 56 draw_target: target, 57 message: "".into(), 58 prefix: "".into(), 59 pos: 0, 60 len, 61 tick: 0, 62 draw_delta: 0, 63 draw_rate: 0, 64 draw_next: 0, 65 status: Status::InProgress, 66 started: Instant::now(), 67 est: Estimate::new(), 68 tick_thread: None, 69 steady_tick: 0, 70 })), 71 } 72 } 73 74 /// A convenience builder-like function for a progress bar with a given style with_style(self, style: ProgressStyle) -> ProgressBar75 pub fn with_style(self, style: ProgressStyle) -> ProgressBar { 76 self.state.lock().unwrap().style = style; 77 self 78 } 79 80 /// A convenience builder-like function for a progress bar with a given prefix with_prefix(self, prefix: impl Into<Cow<'static, str>>) -> ProgressBar81 pub fn with_prefix(self, prefix: impl Into<Cow<'static, str>>) -> ProgressBar { 82 self.state.lock().unwrap().prefix = prefix.into(); 83 self 84 } 85 86 /// A convenience builder-like function for a progress bar with a given message with_message(self, message: impl Into<Cow<'static, str>>) -> ProgressBar87 pub fn with_message(self, message: impl Into<Cow<'static, str>>) -> ProgressBar { 88 self.state.lock().unwrap().message = message.into(); 89 self 90 } 91 92 /// A convenience builder-like function for a progress bar with a given position with_position(self, pos: u64) -> ProgressBar93 pub fn with_position(self, pos: u64) -> ProgressBar { 94 self.state.lock().unwrap().pos = pos; 95 self 96 } 97 98 /// Creates a new spinner 99 /// 100 /// This spinner by default draws directly to stderr. This adds the default spinner style to it. new_spinner() -> ProgressBar101 pub fn new_spinner() -> ProgressBar { 102 let rv = ProgressBar::new(!0); 103 rv.set_style(ProgressStyle::default_spinner()); 104 rv 105 } 106 107 /// Overrides the stored style 108 /// 109 /// This does not redraw the bar. Call [`ProgressBar::tick()`] to force it. set_style(&self, style: ProgressStyle)110 pub fn set_style(&self, style: ProgressStyle) { 111 self.state.lock().unwrap().style = style; 112 } 113 114 /// Spawns a background thread to tick the progress bar 115 /// 116 /// When this is enabled a background thread will regularly tick the progress bar in the given 117 /// interval (in milliseconds). This is useful to advance progress bars that are very slow by 118 /// themselves. 119 /// 120 /// When steady ticks are enabled, calling [`ProgressBar::tick()`] on a progress bar does not 121 /// have any effect. enable_steady_tick(&self, ms: u64)122 pub fn enable_steady_tick(&self, ms: u64) { 123 let mut state = self.state.lock().unwrap(); 124 state.steady_tick = ms; 125 if state.tick_thread.is_some() { 126 return; 127 } 128 129 // Using a weak pointer is required to prevent a potential deadlock. See issue #133 130 let state_arc = Arc::downgrade(&self.state); 131 state.tick_thread = Some(thread::spawn(move || Self::steady_tick(state_arc, ms))); 132 133 ::std::mem::drop(state); 134 // use the side effect of tick to force the bar to tick. 135 self.tick(); 136 } 137 steady_tick(state_arc: Weak<Mutex<ProgressState>>, mut ms: u64)138 fn steady_tick(state_arc: Weak<Mutex<ProgressState>>, mut ms: u64) { 139 loop { 140 thread::sleep(Duration::from_millis(ms)); 141 if let Some(state_arc) = state_arc.upgrade() { 142 let mut state = state_arc.lock().unwrap(); 143 if state.is_finished() || state.steady_tick == 0 { 144 state.steady_tick = 0; 145 state.tick_thread = None; 146 break; 147 } 148 if state.tick != 0 { 149 state.tick = state.tick.saturating_add(1); 150 } 151 ms = state.steady_tick; 152 153 state.draw().ok(); 154 } else { 155 break; 156 } 157 } 158 } 159 160 /// Undoes [`ProgressBar::enable_steady_tick()`] disable_steady_tick(&self)161 pub fn disable_steady_tick(&self) { 162 self.enable_steady_tick(0); 163 } 164 165 /// Limit redrawing of progress bar to every `n` steps 166 /// 167 /// By default, the progress bar will redraw whenever its state advances. This setting is 168 /// helpful in situations where the overhead of redrawing the progress bar dominates the 169 /// computation whose progress is being reported. 170 /// 171 /// If `n` is greater than 0, operations that change the progress bar such as 172 /// [`ProgressBar::tick()`], [`ProgressBar::set_message()`] and [`ProgressBar::set_length()`] 173 /// will no longer cause the progress bar to be redrawn, and will only be shown once the 174 /// position advances by `n` steps. 175 /// 176 /// ```rust,no_run 177 /// # use indicatif::ProgressBar; 178 /// let n = 1_000_000; 179 /// let pb = ProgressBar::new(n); 180 /// pb.set_draw_delta(n / 100); // redraw every 1% of additional progress 181 /// ``` 182 /// 183 /// Note that `ProgressDrawTarget` may impose additional buffering of redraws. set_draw_delta(&self, n: u64)184 pub fn set_draw_delta(&self, n: u64) { 185 let mut state = self.state.lock().unwrap(); 186 state.draw_delta = n; 187 state.draw_next = state.pos.saturating_add(state.draw_delta); 188 } 189 190 /// Sets the refresh rate of progress bar to `n` updates per seconds 191 /// 192 /// This is similar to `set_draw_delta` but automatically adapts to a constant refresh rate 193 /// regardless of how consistent the progress is. 194 /// 195 /// This parameter takes precedence on `set_draw_delta` if different from 0. 196 /// 197 /// ```rust,no_run 198 /// # use indicatif::ProgressBar; 199 /// let n = 1_000_000; 200 /// let pb = ProgressBar::new(n); 201 /// pb.set_draw_rate(25); // aims at redrawing at most 25 times per seconds. 202 /// ``` 203 /// 204 /// Note that the [`ProgressDrawTarget`] may impose additional buffering of redraws. set_draw_rate(&self, n: u64)205 pub fn set_draw_rate(&self, n: u64) { 206 let mut state = self.state.lock().unwrap(); 207 state.draw_rate = n; 208 state.draw_next = state.pos.saturating_add(state.per_sec() / n); 209 } 210 211 /// Manually ticks the spinner or progress bar 212 /// 213 /// This automatically happens on any other change to a progress bar. tick(&self)214 pub fn tick(&self) { 215 self.update_and_draw(|state| { 216 if state.steady_tick == 0 || state.tick == 0 { 217 state.tick = state.tick.saturating_add(1); 218 } 219 }); 220 } 221 222 /// Advances the position of the progress bar by `delta` inc(&self, delta: u64)223 pub fn inc(&self, delta: u64) { 224 self.update_and_draw(|state| { 225 state.pos = state.pos.saturating_add(delta); 226 if state.steady_tick == 0 || state.tick == 0 { 227 state.tick = state.tick.saturating_add(1); 228 } 229 }) 230 } 231 232 /// A quick convenience check if the progress bar is hidden is_hidden(&self) -> bool233 pub fn is_hidden(&self) -> bool { 234 self.state.lock().unwrap().draw_target.is_hidden() 235 } 236 237 /// Indicates that the progress bar finished is_finished(&self) -> bool238 pub fn is_finished(&self) -> bool { 239 self.state.lock().unwrap().is_finished() 240 } 241 242 /// Print a log line above the progress bar 243 /// 244 /// If the progress bar was added to a [`MultiProgress`], the log line will be 245 /// printed above all other progress bars. 246 /// 247 /// Note that if the progress bar is hidden (which by default happens if the progress bar is 248 /// redirected into a file) `println()` will not do anything either. println<I: AsRef<str>>(&self, msg: I)249 pub fn println<I: AsRef<str>>(&self, msg: I) { 250 let mut state = self.state.lock().unwrap(); 251 252 let mut lines: Vec<String> = msg.as_ref().lines().map(Into::into).collect(); 253 let orphan_lines = lines.len(); 254 if state.should_render() && !state.draw_target.is_hidden() { 255 lines.extend(state.style.format_state(&*state)); 256 } 257 258 let draw_state = ProgressDrawState { 259 lines, 260 orphan_lines, 261 finished: state.is_finished(), 262 force_draw: true, 263 move_cursor: false, 264 }; 265 266 state.draw_target.apply_draw_state(draw_state).ok(); 267 } 268 269 /// Sets the position of the progress bar set_position(&self, pos: u64)270 pub fn set_position(&self, pos: u64) { 271 self.update_and_draw(|state| { 272 state.draw_next = pos; 273 state.pos = pos; 274 if state.steady_tick == 0 || state.tick == 0 { 275 state.tick = state.tick.saturating_add(1); 276 } 277 }) 278 } 279 280 /// Sets the length of the progress bar set_length(&self, len: u64)281 pub fn set_length(&self, len: u64) { 282 self.update_and_draw(|state| { 283 state.len = len; 284 }) 285 } 286 287 /// Increase the length of the progress bar inc_length(&self, delta: u64)288 pub fn inc_length(&self, delta: u64) { 289 self.update_and_draw(|state| { 290 state.len = state.len.saturating_add(delta); 291 }) 292 } 293 294 /// Sets the current prefix of the progress bar 295 /// 296 /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template 297 /// (see [`ProgressStyle`]). set_prefix(&self, prefix: impl Into<Cow<'static, str>>)298 pub fn set_prefix(&self, prefix: impl Into<Cow<'static, str>>) { 299 let prefix = prefix.into(); 300 self.update_and_draw(|state| { 301 state.prefix = prefix; 302 if state.steady_tick == 0 || state.tick == 0 { 303 state.tick = state.tick.saturating_add(1); 304 } 305 }) 306 } 307 308 /// Sets the current message of the progress bar 309 /// 310 /// For the message to be visible, the `{msg}` placeholder must be present in the template (see 311 /// [`ProgressStyle`]). set_message(&self, msg: impl Into<Cow<'static, str>>)312 pub fn set_message(&self, msg: impl Into<Cow<'static, str>>) { 313 let msg = msg.into(); 314 self.update_and_draw(|state| { 315 state.message = msg; 316 if state.steady_tick == 0 || state.tick == 0 { 317 state.tick = state.tick.saturating_add(1); 318 } 319 }) 320 } 321 322 /// Creates a new weak reference to this `ProgressBar` downgrade(&self) -> WeakProgressBar323 pub fn downgrade(&self) -> WeakProgressBar { 324 WeakProgressBar { 325 state: Arc::downgrade(&self.state), 326 } 327 } 328 329 /// Resets the ETA calculation 330 /// 331 /// This can be useful if the progress bars made a large jump or was paused for a prolonged 332 /// time. reset_eta(&self)333 pub fn reset_eta(&self) { 334 self.update_and_draw(|state| { 335 state.est.reset(state.pos); 336 }); 337 } 338 339 /// Resets elapsed time reset_elapsed(&self)340 pub fn reset_elapsed(&self) { 341 self.update_and_draw(|state| { 342 state.started = Instant::now(); 343 }); 344 } 345 346 /// Resets all of the progress bar state reset(&self)347 pub fn reset(&self) { 348 self.reset_eta(); 349 self.reset_elapsed(); 350 self.update_and_draw(|state| { 351 state.draw_next = 0; 352 state.pos = 0; 353 state.status = Status::InProgress; 354 }); 355 } 356 357 /// Finishes the progress bar and leaves the current message finish(&self)358 pub fn finish(&self) { 359 self.state.lock().unwrap().finish(); 360 } 361 362 /// Finishes the progress bar at current position and leaves the current message finish_at_current_pos(&self)363 pub fn finish_at_current_pos(&self) { 364 self.state.lock().unwrap().finish_at_current_pos(); 365 } 366 367 /// Finishes the progress bar and sets a message 368 /// 369 /// For the message to be visible, the `{msg}` placeholder must be present in the template (see 370 /// [`ProgressStyle`]). finish_with_message(&self, msg: impl Into<Cow<'static, str>>)371 pub fn finish_with_message(&self, msg: impl Into<Cow<'static, str>>) { 372 self.state.lock().unwrap().finish_with_message(msg); 373 } 374 375 /// Finishes the progress bar and completely clears it finish_and_clear(&self)376 pub fn finish_and_clear(&self) { 377 self.state.lock().unwrap().finish_and_clear(); 378 } 379 380 /// Finishes the progress bar and leaves the current message and progress abandon(&self)381 pub fn abandon(&self) { 382 self.state.lock().unwrap().abandon(); 383 } 384 385 /// Finishes the progress bar and sets a message, and leaves the current progress 386 /// 387 /// For the message to be visible, the `{msg}` placeholder must be present in the template (see 388 /// [`ProgressStyle`]). abandon_with_message(&self, msg: impl Into<Cow<'static, str>>)389 pub fn abandon_with_message(&self, msg: impl Into<Cow<'static, str>>) { 390 self.state.lock().unwrap().abandon_with_message(msg); 391 } 392 393 /// Finishes the progress bar using the behavior stored in the [`ProgressStyle`] 394 /// 395 /// See [`ProgressStyle::on_finish()`]. finish_using_style(&self)396 pub fn finish_using_style(&self) { 397 self.state.lock().unwrap().finish_using_style(); 398 } 399 400 /// Sets a different draw target for the progress bar 401 /// 402 /// This can be used to draw the progress bar to stderr (this is the default): 403 /// 404 /// ```rust,no_run 405 /// # use indicatif::{ProgressBar, ProgressDrawTarget}; 406 /// let pb = ProgressBar::new(100); 407 /// pb.set_draw_target(ProgressDrawTarget::stderr()); 408 /// ``` 409 /// 410 /// **Note:** Calling this method on a [`ProgressBar`] linked with a [`MultiProgress`] (after 411 /// running [`MultiProgress::add`]) will unlink this progress bar. If you don't want this 412 /// behavior, call [`MultiProgress::set_draw_target`] instead. set_draw_target(&self, target: ProgressDrawTarget)413 pub fn set_draw_target(&self, target: ProgressDrawTarget) { 414 let mut state = self.state.lock().unwrap(); 415 state.draw_target.disconnect(); 416 state.draw_target = target; 417 } 418 419 /// Wraps an [`Iterator`] with the progress bar 420 /// 421 /// ```rust,no_run 422 /// # use indicatif::ProgressBar; 423 /// let v = vec![1, 2, 3]; 424 /// let pb = ProgressBar::new(3); 425 /// for item in pb.wrap_iter(v.iter()) { 426 /// // ... 427 /// } 428 /// ``` wrap_iter<It: Iterator>(&self, it: It) -> ProgressBarIter<It>429 pub fn wrap_iter<It: Iterator>(&self, it: It) -> ProgressBarIter<It> { 430 it.progress_with(self.clone()) 431 } 432 433 /// Wraps an [`io::Read`] with the progress bar 434 /// 435 /// ```rust,no_run 436 /// # use std::fs::File; 437 /// # use std::io; 438 /// # use indicatif::ProgressBar; 439 /// # fn test () -> io::Result<()> { 440 /// let source = File::open("work.txt")?; 441 /// let mut target = File::create("done.txt")?; 442 /// let pb = ProgressBar::new(source.metadata()?.len()); 443 /// io::copy(&mut pb.wrap_read(source), &mut target); 444 /// # Ok(()) 445 /// # } 446 /// ``` wrap_read<R: io::Read>(&self, read: R) -> ProgressBarIter<R>447 pub fn wrap_read<R: io::Read>(&self, read: R) -> ProgressBarIter<R> { 448 ProgressBarIter { 449 progress: self.clone(), 450 it: read, 451 } 452 } 453 454 /// Wraps an [`io::Write`] with the progress bar 455 /// 456 /// ```rust,no_run 457 /// # use std::fs::File; 458 /// # use std::io; 459 /// # use indicatif::ProgressBar; 460 /// # fn test () -> io::Result<()> { 461 /// let mut source = File::open("work.txt")?; 462 /// let target = File::create("done.txt")?; 463 /// let pb = ProgressBar::new(source.metadata()?.len()); 464 /// io::copy(&mut source, &mut pb.wrap_write(target)); 465 /// # Ok(()) 466 /// # } 467 /// ``` wrap_write<W: io::Write>(&self, write: W) -> ProgressBarIter<W>468 pub fn wrap_write<W: io::Write>(&self, write: W) -> ProgressBarIter<W> { 469 ProgressBarIter { 470 progress: self.clone(), 471 it: write, 472 } 473 } 474 update_and_draw<F: FnOnce(&mut ProgressState)>(&self, f: F)475 fn update_and_draw<F: FnOnce(&mut ProgressState)>(&self, f: F) { 476 // Delegate to the wrapped state. 477 let mut state = self.state.lock().unwrap(); 478 state.update_and_draw(f); 479 } 480 481 /// Returns the current position position(&self) -> u64482 pub fn position(&self) -> u64 { 483 self.state.lock().unwrap().pos 484 } 485 486 /// Returns the current length length(&self) -> u64487 pub fn length(&self) -> u64 { 488 self.state.lock().unwrap().len 489 } 490 491 /// Returns the current ETA eta(&self) -> Duration492 pub fn eta(&self) -> Duration { 493 self.state.lock().unwrap().eta() 494 } 495 496 /// Returns the current rate of progress per_sec(&self) -> u64497 pub fn per_sec(&self) -> u64 { 498 self.state.lock().unwrap().per_sec() 499 } 500 501 /// Returns the current expected duration duration(&self) -> Duration502 pub fn duration(&self) -> Duration { 503 self.state.lock().unwrap().duration() 504 } 505 506 /// Returns the current elapsed time elapsed(&self) -> Duration507 pub fn elapsed(&self) -> Duration { 508 self.state.lock().unwrap().started.elapsed() 509 } 510 } 511 512 /// Manages multiple progress bars from different threads 513 pub struct MultiProgress { 514 state: Arc<RwLock<MultiProgressState>>, 515 joining: AtomicBool, 516 tx: Sender<(usize, ProgressDrawState)>, 517 rx: Receiver<(usize, ProgressDrawState)>, 518 } 519 520 impl fmt::Debug for MultiProgress { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 522 f.debug_struct("MultiProgress").finish() 523 } 524 } 525 526 unsafe impl Sync for MultiProgress {} 527 528 impl Default for MultiProgress { default() -> MultiProgress529 fn default() -> MultiProgress { 530 MultiProgress::with_draw_target(ProgressDrawTarget::stderr()) 531 } 532 } 533 534 impl MultiProgress { 535 /// Creates a new multi progress object. 536 /// 537 /// Progress bars added to this object by default draw directly to stderr, and refresh 538 /// a maximum of 15 times a second. To change the refresh rate set the draw target to 539 /// one with a different refresh rate. new() -> MultiProgress540 pub fn new() -> MultiProgress { 541 MultiProgress::default() 542 } 543 544 /// Creates a new multi progress object with the given draw target. with_draw_target(draw_target: ProgressDrawTarget) -> MultiProgress545 pub fn with_draw_target(draw_target: ProgressDrawTarget) -> MultiProgress { 546 let (tx, rx) = channel(); 547 MultiProgress { 548 state: Arc::new(RwLock::new(MultiProgressState { 549 objects: Vec::new(), 550 free_set: Vec::new(), 551 ordering: vec![], 552 draw_target, 553 move_cursor: false, 554 })), 555 joining: AtomicBool::new(false), 556 tx, 557 rx, 558 } 559 } 560 561 /// Sets a different draw target for the multiprogress bar. set_draw_target(&self, target: ProgressDrawTarget)562 pub fn set_draw_target(&self, target: ProgressDrawTarget) { 563 let mut state = self.state.write().unwrap(); 564 state.draw_target.disconnect(); 565 state.draw_target = target; 566 } 567 568 /// Set whether we should try to move the cursor when possible instead of clearing lines. 569 /// 570 /// This can reduce flickering, but do not enable it if you intend to change the number of 571 /// progress bars. set_move_cursor(&self, move_cursor: bool)572 pub fn set_move_cursor(&self, move_cursor: bool) { 573 self.state.write().unwrap().move_cursor = move_cursor; 574 } 575 576 /// Adds a progress bar. 577 /// 578 /// The progress bar added will have the draw target changed to a 579 /// remote draw target that is intercepted by the multi progress 580 /// object overriding custom `ProgressDrawTarget` settings. add(&self, pb: ProgressBar) -> ProgressBar581 pub fn add(&self, pb: ProgressBar) -> ProgressBar { 582 self.push(None, pb) 583 } 584 585 /// Inserts a progress bar. 586 /// 587 /// The progress bar inserted at position `index` will have the draw 588 /// target changed to a remote draw target that is intercepted by the 589 /// multi progress object overriding custom `ProgressDrawTarget` settings. 590 /// 591 /// If `index >= MultiProgressState::objects.len()`, the progress bar 592 /// is added to the end of the list. insert(&self, index: usize, pb: ProgressBar) -> ProgressBar593 pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar { 594 self.push(Some(index), pb) 595 } 596 push(&self, pos: Option<usize>, pb: ProgressBar) -> ProgressBar597 fn push(&self, pos: Option<usize>, pb: ProgressBar) -> ProgressBar { 598 let new = MultiObject { 599 done: false, 600 draw_state: None, 601 }; 602 603 let mut state = self.state.write().unwrap(); 604 let idx = match state.free_set.pop() { 605 Some(idx) => { 606 state.objects[idx] = Some(new); 607 idx 608 } 609 None => { 610 state.objects.push(Some(new)); 611 state.objects.len() - 1 612 } 613 }; 614 615 match pos { 616 Some(pos) if pos < state.ordering.len() => state.ordering.insert(pos, idx), 617 _ => state.ordering.push(idx), 618 } 619 620 pb.set_draw_target(ProgressDrawTarget { 621 kind: ProgressDrawTargetKind::Remote { 622 state: self.state.clone(), 623 idx, 624 chan: Mutex::new(self.tx.clone()), 625 }, 626 }); 627 pb 628 } 629 630 /// Removes a progress bar. 631 /// 632 /// The progress bar is removed only if it was previously inserted or added 633 /// by the methods `MultiProgress::insert` or `MultiProgress::add`. 634 /// If the passed progress bar does not satisfy the condition above, 635 /// the `remove` method does nothing. remove(&self, pb: &ProgressBar)636 pub fn remove(&self, pb: &ProgressBar) { 637 let idx = match &pb.state.lock().unwrap().draw_target.kind { 638 ProgressDrawTargetKind::Remote { state, idx, .. } => { 639 // Check that this progress bar is owned by the current MultiProgress. 640 assert!(Arc::ptr_eq(&self.state, state)); 641 *idx 642 } 643 _ => return, 644 }; 645 646 self.state.write().unwrap().remove_idx(idx); 647 } 648 649 /// Waits for all progress bars to report that they are finished. 650 /// 651 /// You need to call this as this will request the draw instructions 652 /// from the remote progress bars. Not calling this will deadlock 653 /// your program. join(&self) -> io::Result<()>654 pub fn join(&self) -> io::Result<()> { 655 self.join_impl(false) 656 } 657 658 /// Works like `join` but clears the progress bar in the end. join_and_clear(&self) -> io::Result<()>659 pub fn join_and_clear(&self) -> io::Result<()> { 660 self.join_impl(true) 661 } 662 join_impl(&self, clear: bool) -> io::Result<()>663 fn join_impl(&self, clear: bool) -> io::Result<()> { 664 if self.joining.load(Ordering::Acquire) { 665 panic!("Already joining!"); 666 } 667 self.joining.store(true, Ordering::Release); 668 669 let move_cursor = self.state.read().unwrap().move_cursor; 670 // Max amount of grouped together updates at once. This is meant 671 // to ensure there isn't a situation where continuous updates prevent 672 // any actual draws happening. 673 const MAX_GROUP_SIZE: usize = 32; 674 let mut recv_peek = None; 675 let mut grouped = 0usize; 676 let mut orphan_lines: Vec<String> = Vec::new(); 677 let mut force_draw = false; 678 while !self.state.read().unwrap().is_done() { 679 let (idx, draw_state) = if let Some(peeked) = recv_peek.take() { 680 peeked 681 } else { 682 self.rx.recv().unwrap() 683 }; 684 force_draw |= draw_state.finished || draw_state.force_draw; 685 686 let mut state = self.state.write().unwrap(); 687 if draw_state.finished { 688 if let Some(ref mut obj) = &mut state.objects[idx] { 689 obj.done = true; 690 } 691 if draw_state.lines.is_empty() { 692 // `finish_and_clear` was called 693 state.remove_idx(idx); 694 } 695 } 696 697 // Split orphan lines out of the draw state, if any 698 let lines = if draw_state.orphan_lines > 0 { 699 let split = draw_state.lines.split_at(draw_state.orphan_lines); 700 orphan_lines.extend_from_slice(split.0); 701 split.1.to_vec() 702 } else { 703 draw_state.lines 704 }; 705 706 let draw_state = ProgressDrawState { 707 lines, 708 orphan_lines: 0, 709 ..draw_state 710 }; 711 712 if let Some(ref mut obj) = &mut state.objects[idx] { 713 obj.draw_state = Some(draw_state); 714 } 715 716 // the rest from here is only drawing, we can skip it. 717 if state.draw_target.is_hidden() { 718 continue; 719 } 720 721 debug_assert!(recv_peek.is_none()); 722 if grouped >= MAX_GROUP_SIZE { 723 // Can't group any more draw calls, proceed to just draw 724 grouped = 0; 725 } else if let Ok(state) = self.rx.try_recv() { 726 // Only group draw calls if there is another draw already queued 727 recv_peek = Some(state); 728 grouped += 1; 729 continue; 730 } else { 731 // No more draws queued, proceed to just draw 732 grouped = 0; 733 } 734 735 let mut lines = vec![]; 736 737 // Make orphaned lines appear at the top, so they can be properly 738 // forgotten. 739 let orphan_lines_count = orphan_lines.len(); 740 lines.append(&mut orphan_lines); 741 742 for index in state.ordering.iter() { 743 if let Some(obj) = &state.objects[*index] { 744 if let Some(ref draw_state) = obj.draw_state { 745 lines.extend_from_slice(&draw_state.lines[..]); 746 } 747 } 748 } 749 750 let finished = state.is_done(); 751 state.draw_target.apply_draw_state(ProgressDrawState { 752 lines, 753 orphan_lines: orphan_lines_count, 754 force_draw: force_draw || orphan_lines_count > 0, 755 move_cursor, 756 finished, 757 })?; 758 759 force_draw = false; 760 } 761 762 if clear { 763 let mut state = self.state.write().unwrap(); 764 state.draw_target.apply_draw_state(ProgressDrawState { 765 lines: vec![], 766 orphan_lines: 0, 767 finished: true, 768 force_draw: true, 769 move_cursor, 770 })?; 771 } 772 773 self.joining.store(false, Ordering::Release); 774 775 Ok(()) 776 } 777 } 778 779 /// A weak reference to a `ProgressBar`. 780 /// 781 /// Useful for creating custom steady tick implementations 782 #[derive(Clone)] 783 pub struct WeakProgressBar { 784 state: Weak<Mutex<ProgressState>>, 785 } 786 787 impl WeakProgressBar { 788 /// Attempts to upgrade the Weak pointer to a [`ProgressBar`], delaying dropping of the inner 789 /// value if successful. Returns `None` if the inner value has since been dropped. 790 /// 791 /// [`ProgressBar`]: struct.ProgressBar.html upgrade(&self) -> Option<ProgressBar>792 pub fn upgrade(&self) -> Option<ProgressBar> { 793 self.state.upgrade().map(|state| ProgressBar { state }) 794 } 795 } 796 797 #[cfg(test)] 798 mod tests { 799 use super::*; 800 801 #[allow(clippy::float_cmp)] 802 #[test] test_pbar_zero()803 fn test_pbar_zero() { 804 let pb = ProgressBar::new(0); 805 assert_eq!(pb.state.lock().unwrap().fraction(), 1.0); 806 } 807 808 #[allow(clippy::float_cmp)] 809 #[test] test_pbar_maxu64()810 fn test_pbar_maxu64() { 811 let pb = ProgressBar::new(!0); 812 assert_eq!(pb.state.lock().unwrap().fraction(), 0.0); 813 } 814 815 #[test] test_pbar_overflow()816 fn test_pbar_overflow() { 817 let pb = ProgressBar::new(1); 818 pb.set_draw_target(ProgressDrawTarget::hidden()); 819 pb.inc(2); 820 pb.finish(); 821 } 822 823 #[test] test_get_position()824 fn test_get_position() { 825 let pb = ProgressBar::new(1); 826 pb.set_draw_target(ProgressDrawTarget::hidden()); 827 pb.inc(2); 828 let pos = pb.position(); 829 assert_eq!(pos, 2); 830 } 831 832 #[test] test_weak_pb()833 fn test_weak_pb() { 834 let pb = ProgressBar::new(0); 835 let weak = pb.downgrade(); 836 assert!(weak.upgrade().is_some()); 837 ::std::mem::drop(pb); 838 assert!(weak.upgrade().is_none()); 839 } 840 841 #[test] test_draw_delta_deadlock()842 fn test_draw_delta_deadlock() { 843 // see issue #187 844 let mpb = MultiProgress::new(); 845 let pb = mpb.add(ProgressBar::new(1)); 846 pb.set_draw_delta(2); 847 drop(pb); 848 mpb.join().unwrap(); 849 } 850 851 #[test] test_abandon_deadlock()852 fn test_abandon_deadlock() { 853 let mpb = MultiProgress::new(); 854 let pb = mpb.add(ProgressBar::new(1)); 855 pb.set_draw_delta(2); 856 pb.abandon(); 857 drop(pb); 858 mpb.join().unwrap(); 859 } 860 861 #[test] late_pb_drop()862 fn late_pb_drop() { 863 let pb = ProgressBar::new(10); 864 let mpb = MultiProgress::new(); 865 // This clone call is required to trigger a now fixed bug. 866 // See <https://github.com/mitsuhiko/indicatif/pull/141> for context 867 #[allow(clippy::redundant_clone)] 868 mpb.add(pb.clone()); 869 } 870 871 #[test] it_can_wrap_a_reader()872 fn it_can_wrap_a_reader() { 873 let bytes = &b"I am an implementation of io::Read"[..]; 874 let pb = ProgressBar::new(bytes.len() as u64); 875 let mut reader = pb.wrap_read(bytes); 876 let mut writer = Vec::new(); 877 io::copy(&mut reader, &mut writer).unwrap(); 878 assert_eq!(writer, bytes); 879 } 880 881 #[test] it_can_wrap_a_writer()882 fn it_can_wrap_a_writer() { 883 let bytes = b"implementation of io::Read"; 884 let mut reader = &bytes[..]; 885 let pb = ProgressBar::new(bytes.len() as u64); 886 let writer = Vec::new(); 887 let mut writer = pb.wrap_write(writer); 888 io::copy(&mut reader, &mut writer).unwrap(); 889 assert_eq!(writer.it, bytes); 890 } 891 892 #[test] progress_bar_sync_send()893 fn progress_bar_sync_send() { 894 let _: Box<dyn Sync> = Box::new(ProgressBar::new(1)); 895 let _: Box<dyn Send> = Box::new(ProgressBar::new(1)); 896 let _: Box<dyn Sync> = Box::new(MultiProgress::new()); 897 let _: Box<dyn Send> = Box::new(MultiProgress::new()); 898 } 899 900 #[test] multi_progress_modifications()901 fn multi_progress_modifications() { 902 let mp = MultiProgress::new(); 903 let p0 = mp.add(ProgressBar::new(1)); 904 let p1 = mp.add(ProgressBar::new(1)); 905 let p2 = mp.add(ProgressBar::new(1)); 906 let p3 = mp.add(ProgressBar::new(1)); 907 mp.remove(&p2); 908 mp.remove(&p1); 909 let p4 = mp.insert(1, ProgressBar::new(1)); 910 911 let state = mp.state.read().unwrap(); 912 // the removed place for p1 is reused 913 assert_eq!(state.objects.len(), 4); 914 assert_eq!(state.objects.iter().filter(|o| o.is_some()).count(), 3); 915 916 // free_set may contain 1 or 2 917 match state.free_set.last() { 918 Some(1) => { 919 assert_eq!(state.ordering, vec![0, 2, 3]); 920 assert_eq!(extract_index(&p4), 2); 921 } 922 Some(2) => { 923 assert_eq!(state.ordering, vec![0, 1, 3]); 924 assert_eq!(extract_index(&p4), 1); 925 } 926 _ => unreachable!(), 927 } 928 929 assert_eq!(extract_index(&p0), 0); 930 assert_eq!(extract_index(&p1), 1); 931 assert_eq!(extract_index(&p2), 2); 932 assert_eq!(extract_index(&p3), 3); 933 } 934 935 #[test] multi_progress_multiple_remove()936 fn multi_progress_multiple_remove() { 937 let mp = MultiProgress::new(); 938 let p0 = mp.add(ProgressBar::new(1)); 939 let p1 = mp.add(ProgressBar::new(1)); 940 // double remove beyond the first one have no effect 941 mp.remove(&p0); 942 mp.remove(&p0); 943 mp.remove(&p0); 944 945 let state = mp.state.read().unwrap(); 946 // the removed place for p1 is reused 947 assert_eq!(state.objects.len(), 2); 948 assert_eq!(state.objects.iter().filter(|obj| obj.is_some()).count(), 1); 949 assert_eq!(state.free_set.last(), Some(&0)); 950 951 assert_eq!(state.ordering, vec![1]); 952 assert_eq!(extract_index(&p0), 0); 953 assert_eq!(extract_index(&p1), 1); 954 } 955 extract_index(pb: &ProgressBar) -> usize956 fn extract_index(pb: &ProgressBar) -> usize { 957 match pb.state.lock().unwrap().draw_target.kind { 958 ProgressDrawTargetKind::Remote { idx, .. } => idx, 959 _ => unreachable!(), 960 } 961 } 962 963 #[test] multi_progress_hidden()964 fn multi_progress_hidden() { 965 let mpb = MultiProgress::with_draw_target(ProgressDrawTarget::hidden()); 966 let pb = mpb.add(ProgressBar::new(123)); 967 pb.finish(); 968 mpb.join().unwrap(); 969 } 970 } 971