blob: 95d538cbf5e224895a2b4fb42fb2cc48f8370b1e [file] [log] [blame]
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),
}
}
}