prodash 31.0.0

A dashboard for visualizing progress of asynchronous and possibly blocking tasks
Documentation
use std::{
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    },
    time::Duration,
};

use crate::{
    messages::MessageLevel,
    progress::{Id, Step, StepShared},
    Count, NestedProgress, Progress, Unit,
};

/// A [`NestedProgress`] implementation which displays progress as it happens without the use of a renderer.
///
/// Note that this incurs considerable performance cost as each progress calls ends up getting the system time
/// to see if progress information should actually be emitted.
pub struct Log {
    name: String,
    id: Id,
    max: Option<usize>,
    unit: Option<Unit>,
    step: StepShared,
    current_level: usize,
    max_level: usize,
    trigger: Arc<AtomicBool>,
}

const EMIT_LOG_EVERY_S: f32 = 0.5;
const SEP: &str = "::";

impl Log {
    /// Create a new instance from `name` while displaying progress information only up to `max_level`.
    pub fn new(name: impl Into<String>, max_level: Option<usize>) -> Self {
        let trigger = Arc::new(AtomicBool::new(true));
        std::thread::spawn({
            let duration = Duration::from_secs_f32(EMIT_LOG_EVERY_S);
            let trigger = Arc::downgrade(&trigger);
            move || {
                while let Some(t) = trigger.upgrade() {
                    t.store(true, Ordering::Relaxed);
                    std::thread::sleep(duration);
                }
            }
        });
        Log {
            name: name.into(),
            id: crate::progress::UNKNOWN,
            current_level: 0,
            max_level: max_level.unwrap_or(usize::MAX),
            max: None,
            step: Default::default(),
            unit: None,
            trigger,
        }
    }
}

impl Log {
    fn maybe_log(&self) {
        if self.current_level > self.max_level {
            return;
        }
        let step = self.step();
        if self.trigger.swap(false, Ordering::Relaxed) {
            match (self.max, &self.unit) {
                (max, Some(unit)) => log::info!("{} → {}", self.name, unit.display(step, max, None)),
                (Some(max), None) => log::info!("{} → {} / {}", self.name, step, max),
                (None, None) => log::info!("{} → {}", self.name, step),
            }
        }
    }
}

impl Count for Log {
    fn set(&self, step: Step) {
        self.step.store(step, Ordering::SeqCst);
        self.maybe_log()
    }

    fn step(&self) -> usize {
        self.step.load(Ordering::Relaxed)
    }

    fn inc_by(&self, step: Step) {
        self.step.fetch_add(step, Ordering::Relaxed);
        self.maybe_log()
    }

    fn counter(&self) -> StepShared {
        self.step.clone()
    }
}

impl Progress for Log {
    fn init(&mut self, max: Option<Step>, unit: Option<Unit>) {
        self.max = max;
        self.unit = unit;
    }
    fn unit(&self) -> Option<Unit> {
        self.unit.clone()
    }

    fn max(&self) -> Option<Step> {
        self.max
    }

    fn set_max(&mut self, max: Option<Step>) -> Option<Step> {
        let prev = self.max;
        self.max = max;
        prev
    }

    fn set_name(&mut self, name: String) {
        self.name = self
            .name
            .split("::")
            .next()
            .map(|parent| format!("{}{}{}", parent.to_owned(), SEP, name))
            .unwrap_or(name);
    }

    fn name(&self) -> Option<String> {
        self.name.split(SEP).nth(1).map(ToOwned::to_owned)
    }

    fn id(&self) -> Id {
        self.id
    }

    fn message(&self, level: MessageLevel, message: String) {
        match level {
            MessageLevel::Info => log::info!("ℹ{} → {}", self.name, message),
            MessageLevel::Failure => log::error!("𐄂{} → {}", self.name, message),
            MessageLevel::Success => log::info!("✓{} → {}", self.name, message),
        }
    }
}

impl NestedProgress for Log {
    type SubProgress = Log;

    fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {
        self.add_child_with_id(name, crate::progress::UNKNOWN)
    }

    fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {
        Log {
            name: format!("{}{}{}", self.name, SEP, Into::<String>::into(name)),
            id,
            current_level: self.current_level + 1,
            max_level: self.max_level,
            step: Default::default(),
            max: None,
            unit: None,
            trigger: Arc::clone(&self.trigger),
        }
    }
}