compare-dir 0.6.9

A high-performance directory comparison tool and library
Documentation
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::path::Path;
use std::time::Duration;

const SPINNER_STYLE: &str = "{elapsed_precise} {pos:>7} {spinner:.green} {msg}";
const NORMAL_STYLE: &str =
    "{elapsed_precise} +{eta:>3} {percent:>3}% {pos:>7}/{len:7} {bar:40.cyan/blue} {msg}";
const FILE_STYLE: &str = "  {elapsed:>3} +{eta:>3} {percent:>3}% {msg}";

#[derive(Debug, Clone)]
pub(crate) struct Progress {
    inner: Option<ProgressBar>,
    multi: Option<MultiProgress>,
}

impl Progress {
    pub fn none() -> Self {
        Self {
            inner: None,
            multi: None,
        }
    }

    pub fn set_message(&self, msg: impl Into<String>) {
        if let Some(inner) = &self.inner {
            inner.set_message(msg.into());
        }
    }

    pub fn inc(&self, amount: u64) {
        if let Some(inner) = &self.inner {
            inner.inc(amount);
        }
    }

    pub fn length(&self) -> Option<u64> {
        self.inner.as_ref().and_then(|inner| inner.length())
    }

    pub fn set_length(&self, len: u64) {
        if let Some(inner) = &self.inner {
            if len > 0 && inner.length().is_none() {
                inner.set_style(ProgressStyle::with_template(NORMAL_STYLE).unwrap());
            }
            inner.set_length(len);
        }
    }

    pub fn finish(&self) {
        if let Some(inner) = &self.inner {
            inner.finish();
            if let Some(multi) = &self.multi {
                multi.remove(inner);
            }
        }
    }

    pub fn suspend<F, R>(&self, f: F) -> R
    where
        F: FnOnce() -> R,
    {
        if let Some(multi) = &self.multi {
            multi.suspend(f)
        } else if let Some(inner) = &self.inner {
            inner.suspend(f)
        } else {
            f()
        }
    }
}

#[derive(Debug)]
pub struct ProgressBuilder {
    multi: MultiProgress,
    pub is_file_enabled: bool,
}

impl ProgressBuilder {
    pub fn new() -> Self {
        Self {
            multi: MultiProgress::new(),
            is_file_enabled: false,
        }
    }

    pub(crate) fn add_spinner(&self) -> Progress {
        let progress = self.multi.add(ProgressBar::new_spinner());
        progress.enable_steady_tick(Duration::from_millis(120));
        progress.set_style(ProgressStyle::with_template(SPINNER_STYLE).unwrap());
        Progress {
            inner: Some(progress),
            multi: Some(self.multi.clone()),
        }
    }

    pub(crate) fn add_file(&self, path: &Path, file_size: u64) -> Progress {
        if !self.is_file_enabled {
            return Progress::none();
        }
        let progress = self.multi.add(ProgressBar::new(file_size));
        progress.set_style(ProgressStyle::with_template(FILE_STYLE).unwrap());
        if let Some(parent) = path.parent()
            && let Some(file_name) = path.file_name()
        {
            progress.set_message(format!(
                "{} ({})",
                file_name.to_string_lossy(),
                parent.to_string_lossy()
            ));
        } else {
            progress.set_message(path.to_string_lossy().to_string());
        }
        Progress {
            inner: Some(progress),
            multi: Some(self.multi.clone()),
        }
    }
}

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