rustic-rs 0.11.2

rustic - fast, encrypted, deduplicated backups powered by Rust
Documentation
use std::io::Stdout;
use std::sync::{Arc, RwLock};
use std::time::{Duration, SystemTime};

use bytesize::ByteSize;
use ratatui::{Terminal, backend::CrosstermBackend};
use rustic_core::{Progress, ProgressBars, ProgressType, RusticProgress};

use super::widgets::{Draw, popup_gauge, popup_text};

#[derive(Debug, Clone)]
pub struct TuiProgressBars {
    pub terminal: Arc<RwLock<Terminal<CrosstermBackend<Stdout>>>>,
}

impl TuiProgressBars {
    fn as_progress(&self, progress_type: ProgressType, prefix: String) -> Progress {
        let progress = TuiProgress {
            terminal: self.terminal.clone(),
            data: Arc::new(RwLock::new(CounterData::new(prefix))),
            progress_type,
        };
        progress.popup();
        Progress::new(progress)
    }
}

impl ProgressBars for TuiProgressBars {
    fn progress(&self, progress_type: ProgressType, prefix: &str) -> Progress {
        self.as_progress(progress_type, prefix.to_string())
    }
}

#[derive(Debug)]
struct CounterData {
    prefix: String,
    begin: SystemTime,
    length: Option<u64>,
    count: u64,
}

impl CounterData {
    fn new(prefix: String) -> Self {
        Self {
            prefix,
            begin: SystemTime::now(),
            length: None,
            count: 0,
        }
    }
}

#[derive(Clone, Debug)]
pub struct TuiProgress {
    terminal: Arc<RwLock<Terminal<CrosstermBackend<Stdout>>>>,
    data: Arc<RwLock<CounterData>>,
    progress_type: ProgressType,
}

fn fmt_duration(d: Duration) -> String {
    let seconds = d.as_secs();
    let (minutes, seconds) = (seconds / 60, seconds % 60);
    let (hours, minutes) = (minutes / 60, minutes % 60);
    format!("[{hours:02}:{minutes:02}:{seconds:02}]")
}

impl TuiProgress {
    fn popup(&self) {
        let data = self.data.read().unwrap();
        let elapsed = data.begin.elapsed().unwrap();
        let length = data.length;
        let count = data.count;
        let ratio = match length {
            None | Some(0) => 0.0,
            Some(l) => count as f64 / l as f64,
        };
        let eta = match ratio {
            r if r < 0.01 => " ETA: -".to_string(),
            r if r > 0.999_999 => String::new(),
            r => {
                format!(
                    " ETA: {}",
                    fmt_duration(Duration::from_secs(1) + elapsed.div_f64(r / (1.0 - r)))
                )
            }
        };
        let prefix = &data.prefix;
        let message = match self.progress_type {
            ProgressType::Spinner => {
                format!("{} {prefix}", fmt_duration(elapsed))
            }
            ProgressType::Counter => {
                format!(
                    "{} {prefix} {}{}{eta}",
                    fmt_duration(elapsed),
                    count,
                    length.map_or(String::new(), |l| format!("/{l}"))
                )
            }
            ProgressType::Bytes => {
                format!(
                    "{} {prefix} {}{}{eta}",
                    fmt_duration(elapsed),
                    ByteSize(count).display(),
                    length.map_or(String::new(), |l| format!("/{}", ByteSize(l).display()))
                )
            }
        };
        drop(data);

        let mut terminal = self.terminal.write().unwrap();
        _ = terminal
            .draw(|f| {
                let area = f.area();
                match self.progress_type {
                    ProgressType::Spinner => {
                        let mut popup = popup_text("progress", message.into());
                        popup.draw(area, f);
                    }
                    ProgressType::Counter | ProgressType::Bytes => {
                        let mut popup = popup_gauge("progress", message.into(), ratio);
                        popup.draw(area, f);
                    }
                }
            })
            .unwrap();
    }
}

impl RusticProgress for TuiProgress {
    fn is_hidden(&self) -> bool {
        false
    }
    fn set_length(&self, len: u64) {
        self.data.write().unwrap().length = Some(len);
        self.popup();
    }
    fn set_title(&self, title: &str) {
        self.data.write().unwrap().prefix = title.to_string();
        self.popup();
    }

    fn inc(&self, inc: u64) {
        self.data.write().unwrap().count += inc;
        self.popup();
    }
    fn finish(&self) {}
}