use std::io;
use std::iter::repeat;
use std::borrow::Cow;
use std::cell::RefCell;
use std::time::{Duration, Instant};
use std::sync::mpsc::{channel, Sender, Receiver};
use std::sync::atomic::{AtomicBool, Ordering};
use parking_lot::RwLock;
use term::Term;
use utils::{expand_template, Estimate, duration_to_secs, secs_to_duration};
use format::{FormattedDuration, HumanDuration, HumanBytes};
use ansistyle::{Style, measure_text_width};
#[derive(Clone)]
pub struct ProgressStyle {
tick_chars: Vec<char>,
progress_chars: Vec<char>,
template: Cow<'static, str>,
}
#[derive(Clone)]
struct ProgressDrawState {
pub lines: Vec<String>,
pub finished: bool,
pub force_draw: bool,
pub ts: Instant,
}
enum Status {
InProgress,
DoneVisible,
DoneHidden,
}
enum ProgressDrawTargetKind {
Term(Term, Option<ProgressDrawState>, Option<Duration>),
Remote(usize, Sender<(usize, ProgressDrawState)>),
Hidden,
}
pub struct ProgressDrawTarget {
kind: ProgressDrawTargetKind,
}
impl ProgressDrawTarget {
pub fn stdout() -> ProgressDrawTarget {
ProgressDrawTarget::to_term(Term::buffered_stdout(), Some(15))
}
pub fn stderr() -> ProgressDrawTarget {
ProgressDrawTarget::to_term(Term::buffered_stderr(), Some(15))
}
pub fn to_term(term: Term, refresh_rate: Option<u64>) -> ProgressDrawTarget {
let rate = refresh_rate.map(|x| Duration::from_millis(1000 / x));
ProgressDrawTarget {
kind: ProgressDrawTargetKind::Term(term, None, rate),
}
}
pub fn hidden() -> ProgressDrawTarget {
ProgressDrawTarget {
kind: ProgressDrawTargetKind::Hidden,
}
}
pub fn is_hidden(&self) -> bool {
match self.kind {
ProgressDrawTargetKind::Hidden => true,
ProgressDrawTargetKind::Term(ref term, ..) => !term.is_term(),
_ => false,
}
}
fn apply_draw_state(&mut self, draw_state: ProgressDrawState) -> io::Result<()> {
if self.is_hidden() {
return Ok(());
}
match self.kind {
ProgressDrawTargetKind::Term(ref term, ref mut last_state, rate) => {
let last_draw = last_state.as_ref().map(|x| x.ts);
if draw_state.finished ||
draw_state.force_draw ||
rate.is_none() ||
last_draw.is_none() ||
last_draw.unwrap().elapsed() > rate.unwrap() {
if let Some(ref last_state) = *last_state {
last_state.clear_term(term)?;
}
draw_state.draw_to_term(term)?;
term.flush()?;
*last_state = Some(draw_state);
}
}
ProgressDrawTargetKind::Remote(idx, ref chan) => {
chan.send((idx, draw_state)).unwrap();
}
ProgressDrawTargetKind::Hidden => {}
}
Ok(())
}
}
impl ProgressDrawState {
pub fn clear_term(&self, term: &Term) -> io::Result<()> {
term.clear_last_lines(self.lines.len())
}
pub fn draw_to_term(&self, term: &Term) -> io::Result<()> {
for line in &self.lines {
term.write_line(line)?;
}
Ok(())
}
}
impl ProgressStyle {
pub fn default_bar() -> ProgressStyle {
ProgressStyle {
tick_chars: "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈ ".chars().collect(),
progress_chars: "██░".chars().collect(),
template: Cow::Borrowed("{wide_bar} {pos}/{len}"),
}
}
pub fn default_spinner() -> ProgressStyle {
ProgressStyle {
tick_chars: "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈ ".chars().collect(),
progress_chars: "██░".chars().collect(),
template: Cow::Borrowed("{spinner} {msg}"),
}
}
pub fn tick_chars(mut self, s: &str) -> ProgressStyle {
self.tick_chars = s.chars().collect();
self
}
pub fn progress_chars(mut self, s: &str) -> ProgressStyle {
self.progress_chars = s.chars().collect();
self
}
pub fn template(mut self, s: &str) -> ProgressStyle {
self.template = Cow::Owned(s.into());
self
}
}
impl ProgressStyle {
pub fn get_tick_char(&self, idx: u64) -> char {
self.tick_chars[(idx as usize) % (self.tick_chars.len() - 1)]
}
pub fn get_final_tick_char(&self) -> char {
self.tick_chars[self.tick_chars.len() - 1]
}
fn format_bar(&self, state: &ProgressState, width: usize,
alt_style: Option<&Style>) -> String {
let pct = state.percent();
let mut fill = (pct * width as f32) as usize;
let mut head = 0;
if fill > 0 && !state.is_finished() {
fill -= 1;
head = 1;
}
let bar = repeat(state.style.progress_chars[0])
.take(fill).collect::<String>();
let cur = if head == 1 {
state.style.progress_chars[1].to_string()
} else {
"".into()
};
let rest = repeat(state.style.progress_chars[2])
.take(width - fill - head).collect::<String>();
format!("{}{}{}", bar, cur, alt_style.unwrap_or(&Style::new()).apply_to(rest))
}
fn format_state(&self, state: &ProgressState) -> Vec<String> {
let (pos, len) = state.position();
let mut rv = vec![];
for line in self.template.lines() {
let need_wide_bar = RefCell::new(None);
let s = expand_template(line, |var| {
let key = var.key;
if key == "wide_bar" {
*need_wide_bar.borrow_mut() = Some(var.alt_style.clone());
"\x00".into()
} else if key == "bar" {
self.format_bar(state, var.width.unwrap_or(20),
var.alt_style.as_ref())
} else if key == "spinner" {
state.current_tick_char().to_string()
} else if key == "msg" {
state.message().to_string()
} else if key == "prefix" {
state.prefix().to_string()
} else if key == "pos" {
pos.to_string()
} else if key == "len" {
len.to_string()
} else if key == "bytes" {
format!("{}", HumanBytes(state.pos))
} else if key == "total_bytes" {
format!("{}", HumanBytes(state.len))
} else if key == "elapsed_precise" {
format!("{}", FormattedDuration(state.started.elapsed()))
} else if key == "elapsed" {
format!("{:#}", HumanDuration(state.started.elapsed()))
} else if key == "eta_precise" {
format!("{}", FormattedDuration(state.eta()))
} else if key == "eta" {
format!("{:#}", HumanDuration(state.eta()))
} else {
"".into()
}
});
rv.push(if let Some(ref style) = *need_wide_bar.borrow() {
let total_width = state.width();
let bar_width = total_width - measure_text_width(&s);
s.replace("\x00", &self.format_bar(state, bar_width, style.as_ref()))
} else {
s.to_string()
});
}
rv
}
}
struct ProgressState {
style: ProgressStyle,
draw_target: ProgressDrawTarget,
width: Option<u16>,
message: String,
prefix: String,
pos: u64,
len: u64,
tick: u64,
status: Status,
started: Instant,
est: Estimate,
}
impl ProgressState {
pub fn current_tick_char(&self) -> char {
if self.is_finished() {
self.style.get_final_tick_char()
} else {
self.style.get_tick_char(self.tick)
}
}
pub fn is_finished(&self) -> bool {
match self.status {
Status::InProgress => false,
Status::DoneVisible => true,
Status::DoneHidden => true,
}
}
pub fn should_render(&self) -> bool {
match self.status {
Status::DoneHidden => false,
_ => true,
}
}
pub fn percent(&self) -> f32 {
if self.len == !0 {
0.0
} else {
self.pos as f32 / self.len as f32
}
}
pub fn position(&self) -> (u64, u64) {
(self.pos, self.len)
}
pub fn message(&self) -> &str {
&self.message
}
pub fn prefix(&self) -> &str {
&self.prefix
}
pub fn width(&self) -> usize {
if let Some(width) = self.width {
width as usize
} else {
Term::stdout().size().1 as usize
}
}
pub fn avg_time_per_step(&self) -> Duration {
self.est.time_per_step()
}
pub fn eta(&self) -> Duration {
if self.len == !0 || self.is_finished() {
return Duration::new(0, 0);
}
let t = duration_to_secs(self.avg_time_per_step());
secs_to_duration(t * (self.len - self.pos) as f64 + 0.75)
}
}
pub struct ProgressBar {
state: RwLock<ProgressState>,
}
unsafe impl Sync for ProgressBar {}
impl ProgressBar {
pub fn new(len: u64) -> ProgressBar {
ProgressBar {
state: RwLock::new(ProgressState {
style: ProgressStyle::default_bar(),
draw_target: ProgressDrawTarget::stdout(),
width: None,
message: "".into(),
prefix: "".into(),
pos: 0,
len: len,
tick: 0,
status: Status::InProgress,
started: Instant::now(),
est: Estimate::new(),
}),
}
}
pub fn hidden() -> ProgressBar {
let rv = ProgressBar::new(!0);
rv.set_draw_target(ProgressDrawTarget::hidden());
rv
}
pub fn new_spinner() -> ProgressBar {
let rv = ProgressBar::new(!0);
rv.set_style(ProgressStyle::default_spinner());
rv
}
pub fn set_style(&self, style: ProgressStyle) {
self.state.write().style = style;
}
pub fn tick(&self) {
self.update_and_draw(|mut state| {
state.tick += 1;
});
}
pub fn inc(&self, delta: u64) {
self.update_and_draw(|mut state| {
state.pos += delta;
state.tick += 1;
})
}
pub fn set_position(&self, pos: u64) {
self.update_and_draw(|mut state| {
state.pos = pos;
state.tick += 1;
})
}
pub fn set_length(&self, len: u64) {
self.update_and_draw(|mut state| {
state.len = len;
})
}
pub fn set_prefix(&self, prefix: &str) {
let prefix = prefix.to_string();
self.update_and_draw(|mut state| {
state.prefix = prefix;
state.tick += 1;
})
}
pub fn set_message(&self, msg: &str) {
let msg = msg.to_string();
self.update_and_draw(|mut state| {
state.message = msg;
state.tick += 1;
})
}
pub fn finish(&self) {
self.update_and_draw(|mut state| {
state.pos = state.len;
state.status = Status::DoneVisible;
});
}
pub fn finish_with_message(&self, msg: &str) {
let msg = msg.to_string();
self.update_and_draw(|mut state| {
state.message = msg;
state.pos = state.len;
state.status = Status::DoneVisible;
});
}
pub fn finish_and_clear(&self) {
self.update_and_draw(|mut state| {
state.pos = state.len;
state.status = Status::DoneHidden;
});
}
pub fn set_draw_target(&self, target: ProgressDrawTarget) {
self.state.write().draw_target = target;
}
fn update_and_draw<F: FnOnce(&mut ProgressState)>(&self, f: F) {
{
let mut state = self.state.write();
let old_pos = state.pos;
f(&mut state);
let new_pos = state.pos;
if new_pos != old_pos {
state.est.record_step(new_pos);
}
}
self.draw().ok();
}
fn draw(&self) -> io::Result<()> {
let mut state = self.state.write();
if state.draw_target.is_hidden() {
return Ok(());
}
let draw_state = ProgressDrawState {
lines: if state.should_render() {
state.style.format_state(&*state)
} else {
vec![]
},
finished: state.is_finished(),
force_draw: false,
ts: Instant::now(),
};
state.draw_target.apply_draw_state(draw_state)
}
}
struct MultiObject {
done: bool,
draw_state: Option<ProgressDrawState>,
}
struct MultiProgressState {
objects: Vec<MultiObject>,
draw_target: ProgressDrawTarget,
}
pub struct MultiProgress {
state: RwLock<MultiProgressState>,
joining: AtomicBool,
tx: Sender<(usize, ProgressDrawState)>,
rx: Receiver<(usize, ProgressDrawState)>,
}
unsafe impl Sync for MultiProgress {}
impl MultiProgress {
pub fn new() -> MultiProgress {
let (tx, rx) = channel();
MultiProgress {
state: RwLock::new(MultiProgressState {
objects: vec![],
draw_target: ProgressDrawTarget::stdout(),
}),
joining: AtomicBool::new(false),
tx: tx,
rx: rx,
}
}
pub fn set_draw_target(&self, target: ProgressDrawTarget) {
self.state.write().draw_target = target;
}
pub fn add(&self, bar: ProgressBar) -> ProgressBar {
let mut state = self.state.write();
let idx = state.objects.len();
state.objects.push(MultiObject {
done: false,
draw_state: None,
});
bar.set_draw_target(ProgressDrawTarget {
kind: ProgressDrawTargetKind::Remote(idx, self.tx.clone()),
});
bar
}
pub fn join(&self) -> io::Result<()> {
self.join_impl(false)
}
pub fn join_and_clear(&self) -> io::Result<()> {
self.join_impl(true)
}
fn is_done(&self) -> bool {
let state = self.state.read();
if state.objects.is_empty() {
return true;
}
for obj in &state.objects {
if !obj.done {
return false;
}
}
true
}
fn join_impl(&self, clear: bool) -> io::Result<()> {
if self.joining.load(Ordering::Acquire) {
panic!("Already joining!");
}
self.joining.store(true, Ordering::Release);
while !self.is_done() {
let (idx, draw_state) = self.rx.recv().unwrap();
let ts = draw_state.ts;
let force_draw = draw_state.finished || draw_state.force_draw;
let mut state = self.state.write();
if draw_state.finished {
state.objects[idx].done = true;
}
state.objects[idx].draw_state = Some(draw_state);
if state.draw_target.is_hidden() {
continue;
}
let mut lines = vec![];
for obj in state.objects.iter() {
if let Some(ref draw_state) = obj.draw_state {
lines.extend_from_slice(&draw_state.lines[..]);
}
}
let finished = !state.objects.iter().any(|ref x| x.done);
state.draw_target.apply_draw_state(ProgressDrawState {
lines: lines,
force_draw: force_draw,
finished: finished,
ts: ts,
})?;
}
if clear {
let mut state = self.state.write();
state.draw_target.apply_draw_state(ProgressDrawState {
lines: vec![],
finished: true,
force_draw: true,
ts: Instant::now(),
})?;
}
self.joining.store(false, Ordering::Release);
Ok(())
}
}
impl Drop for ProgressBar {
fn drop(&mut self) {
if self.state.read().is_finished() {
return;
}
self.update_and_draw(|mut state| {
state.status = Status::DoneHidden;
});
}
}