bitbottle 0.10.0

a modern archive file format
Documentation
use std::cell::RefCell;
use std::rc::Rc;
use std::time::{Duration, Instant};
use term_size::dimensions_stderr;
use crate::cli::pad_truncate;

const EIGHT: [char; 9] = [
    ' ',
    '\u{258f}', '\u{258e}', '\u{258d}', '\u{258c}',
    '\u{258b}', '\u{258a}', '\u{2589}', '\u{2588}'
];

pub struct ProgressBar {
    width: usize,
    amount: f64,  // 0.0 -> 1.0
    buffer: String,
}

impl ProgressBar {
    pub fn new(width: usize) -> ProgressBar {
        ProgressBar { width, amount: 0f64, buffer: String::new() }
    }

    pub fn update(&mut self, amount: f64) -> &str {
        self.amount = amount;
        self.render()
    }

    pub fn render(&mut self) -> &str {
        let mut units = (self.amount * self.width as f64 * 8f64).floor() as isize;
        self.buffer.truncate(0);
        for _i in 0..self.width {
            self.buffer.push(EIGHT[units.clamp(0, 8) as usize]);
            units -= 8;
        }
        &self.buffer
    }
}


const BAR_WIDTH: usize = 32;

pub struct ProgressLine {
    bar: ProgressBar,
    start_time: Instant,
    last_update: Instant,
    pub debounce: Duration,
    width: usize,

    // configurable on the fly:
    message: String,
    pub show_bar: bool,
    pub show_ever: bool,
    completion: f64,
}

impl ProgressLine {
    pub fn new() -> ProgressLine {
        let bar = ProgressBar::new(BAR_WIDTH);
        let start_time = Instant::now();
        let last_update = start_time - Duration::from_secs(3600);
        let debounce = Duration::from_millis(100);
        let width = dimensions_stderr().map(|(cols, _rows)| cols - 1).unwrap_or(79);
        let message = String::default();
        let show_bar = true;
        let show_ever = true;
        let completion = 0f64;
        ProgressLine { bar, start_time, last_update, debounce, width, message, show_bar, show_ever, completion }
    }

    pub fn to_shared(self) -> Rc<RefCell<ProgressLine>> {
        Rc::new(RefCell::new(self))
    }

    pub fn update(&mut self, completion: f64, message: String) -> &mut Self {
        self.completion = completion;
        self.message = message;
        self
    }

    pub fn complete(&mut self, completion: f64) -> &mut Self {
        self.completion = completion;
        self
    }

    pub fn force_update(&mut self) -> &mut Self {
        self.last_update -= self.debounce;
        self
    }

    pub fn display(&mut self) {
        if !self.show_ever { return; }

        let now = Instant::now();
        if now.duration_since(self.last_update) < self.debounce {
            return;
        }

        let duration = now.duration_since(self.start_time);
        let seconds = duration.as_secs();
        let minutes = seconds / 60;
        let hours = minutes / 60;
        let time = format!("{:02}:{:02}:{:02}", hours, minutes % 60, seconds % 60);

        let bar = if self.show_bar {
            format!("\u{2507}{}\u{2507} {:>3}% ",
                self.bar.update(self.completion),
                (self.completion * 100f64).floor() as usize
            )
        } else {
            String::new()
        };

        // time: "[hh:mm:ss] " = 11 chars
        let width = 11 + if self.show_bar { BAR_WIDTH + 8 } else { 0 };
        eprint!("\r[{}] {}{}", time, bar, pad_truncate(&self.message, self.width - width));

        self.last_update = now;
    }

    pub fn clear(&mut self) {
        if self.show_ever {
            eprint!("\r{}\r", pad_truncate("", self.width));
        }
        self.force_update();
    }
}

impl Default for ProgressLine {
    fn default() -> Self {
        ProgressLine::new()
    }
}