use crate::bar::progress::ProgressBar;
use crate::bar::render::Renderer;
use crate::terminal::writer::DrawTarget;
use std::fmt;
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;
use std::time::Duration;
struct MultiState {
bars: Vec<ProgressBar>,
renderer: Renderer,
move_cursor: bool,
}
#[derive(Clone)]
pub struct MultiProgress {
inner: Arc<Mutex<MultiState>>,
}
impl MultiProgress {
pub fn new() -> Self {
Self::with_draw_target(DrawTarget::stderr())
}
pub fn with_draw_target(target: DrawTarget) -> Self {
Self {
inner: Arc::new(Mutex::new(MultiState {
bars: Vec::new(),
renderer: Renderer::new(target),
move_cursor: true,
})),
}
}
pub fn add(&self, pb: ProgressBar) -> ProgressBar {
self.insert(usize::MAX, pb)
}
pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar {
pb.redirect_target(DrawTarget::hidden());
let mut inner = self.lock_inner();
let idx = index.min(inner.bars.len());
inner.bars.insert(idx, pb.clone());
Self::render_locked(&mut inner);
pb
}
pub fn insert_before(&self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar {
let index = self
.lock_inner()
.bars
.iter()
.position(|bar| bar.same_bar(before))
.unwrap_or(0);
self.insert(index, pb)
}
pub fn insert_after(&self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar {
let index = self
.lock_inner()
.bars
.iter()
.position(|bar| bar.same_bar(after))
.map_or(usize::MAX, |idx| idx + 1);
self.insert(index, pb)
}
pub fn remove(&self, pb: &ProgressBar) {
let mut inner = self.lock_inner();
inner.bars.retain(|bar| !bar.same_bar(pb));
Self::render_locked(&mut inner);
}
pub fn println(&self, msg: &str) {
self.lock_inner().renderer.println(msg);
}
pub fn clear(&self) {
self.lock_inner().renderer.clear();
}
pub fn join(&self) {
loop {
let done = {
let mut inner = self.lock_inner();
Self::render_locked(&mut inner);
inner.bars.iter().all(ProgressBar::is_complete_for_join)
};
if done {
break;
}
thread::sleep(Duration::from_millis(50));
}
}
pub fn set_move_cursor(&self, val: bool) {
self.lock_inner().move_cursor = val;
}
fn render_locked(inner: &mut MultiState) {
let width = inner.renderer.target.width();
let lines: Vec<String> = inner
.bars
.iter()
.map(|bar| bar.render_line_for_width(width))
.collect();
if lines.is_empty() {
inner.renderer.clear();
} else {
inner.renderer.draw(&lines.join("\n"));
}
}
fn lock_inner(&self) -> MutexGuard<'_, MultiState> {
match self.inner.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
}
}
impl Default for MultiProgress {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for MultiProgress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let inner = self.lock_inner();
f.debug_struct("MultiProgress")
.field("bars", &inner.bars.len())
.field("move_cursor", &inner.move_cursor)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hidden_multi() -> MultiProgress {
MultiProgress::with_draw_target(DrawTarget::hidden())
}
#[test]
fn test_add_bar_to_multi() {
let multi = hidden_multi();
let pb = multi.add(ProgressBar::hidden());
assert!(pb.is_hidden());
assert_eq!(multi.lock_inner().bars.len(), 1);
}
#[test]
fn test_remove_bar() {
let multi = hidden_multi();
let pb = multi.add(ProgressBar::hidden());
multi.remove(&pb);
assert_eq!(multi.lock_inner().bars.len(), 0);
}
#[test]
fn test_insert_before() {
let multi = hidden_multi();
let first = multi.add(ProgressBar::hidden());
let second = multi.insert_before(&first, ProgressBar::hidden());
let inner = multi.lock_inner();
assert!(inner.bars[0].same_bar(&second));
}
#[test]
fn test_insert_after() {
let multi = hidden_multi();
let first = multi.add(ProgressBar::hidden());
let second = multi.insert_after(&first, ProgressBar::hidden());
let inner = multi.lock_inner();
assert!(inner.bars[1].same_bar(&second));
}
#[test]
fn test_join_exits_when_all_finish() {
let multi = hidden_multi();
let pb = multi.add(ProgressBar::hidden());
pb.finish();
multi.join();
assert!(pb.is_finished());
}
#[test]
fn test_multi_println() {
let multi = hidden_multi();
multi.println("hello");
assert_eq!(multi.lock_inner().bars.len(), 0);
}
}