use std::{ fmt::Debug, ops::Deref, sync::{ atomic::{AtomicUsize, Ordering}, Arc, }, time::SystemTime, }; use parking_lot::Mutex; use crate::{ messages::MessageLevel, progress::{Id, State, Step, StepShared, Task, Value}, tree::Item, unit::Unit, }; impl Drop for Item { fn drop(&mut self) { self.tree.remove(&self.key); } } impl Debug for Item { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Item") .field("key", &self.key) .field("value", &self.value) .finish_non_exhaustive() } } impl Item { /// Initialize the Item for receiving progress information. /// /// If `max` is `Some(…)`, it will be treated as upper bound. When progress is [set(…)](./struct.Item.html#method.set) /// it should not exceed the given maximum. /// If `max` is `None`, the progress is unbounded. Use this if the amount of work cannot accurately /// be determined. /// /// If `unit` is `Some(…)`, it is used for display purposes only. It should be using the plural. /// /// If this method is never called, this `Item` will serve as organizational unit, useful to add more structure /// to the progress tree. /// /// **Note** that this method can be called multiple times, changing the bounded-ness and unit at will. pub fn init(&mut self, max: Option, unit: Option) { #[cfg(feature = "progress-tree-hp-hashmap")] { if let Some(mut r) = self.tree.get_mut(&self.key) { self.value.store(0, Ordering::SeqCst); r.value_mut().progress = (max.is_some() || unit.is_some()).then(|| Value { done_at: max, unit, step: Arc::clone(&self.value), ..Default::default() }) }; } #[cfg(not(feature = "progress-tree-hp-hashmap"))] { self.tree.get_mut(&self.key, |v| { self.value.store(0, Ordering::SeqCst); v.progress = (max.is_some() || unit.is_some()).then(|| Value { done_at: max, unit, step: Arc::clone(&self.value), ..Default::default() }); }); } } fn alter_progress(&mut self, f: impl FnMut(&mut Value)) { #[cfg(feature = "progress-tree-hp-hashmap")] { if let Some(mut r) = self.tree.get_mut(&self.key) { // NOTE: since we wrap around, if there are more tasks than we can have IDs for, // and if all these tasks are still alive, two progress trees may see the same ID // when these go out of scope, they delete the key and the other tree will not find // its value anymore. Besides, it's probably weird to see tasks changing their progress // all the time… r.value_mut().progress.as_mut().map(f); }; } #[cfg(not(feature = "progress-tree-hp-hashmap"))] { self.tree.get_mut(&self.key, |v| { v.progress.as_mut().map(f); }); } } /// Set the name of this task's progress to the given `name`. pub fn set_name(&mut self, name: impl Into) { #[cfg(feature = "progress-tree-hp-hashmap")] { if let Some(mut r) = self.tree.get_mut(&self.key) { r.value_mut().name = name.into(); }; } #[cfg(not(feature = "progress-tree-hp-hashmap"))] { self.tree.get_mut(&self.key, |v| { v.name = name.into(); }); } } /// Get the name of this task's progress pub fn name(&self) -> Option { #[cfg(feature = "progress-tree-hp-hashmap")] { self.tree.get(&self.key).map(|r| r.value().name.to_owned()) } #[cfg(not(feature = "progress-tree-hp-hashmap"))] { self.tree.get(&self.key, |v| v.name.to_owned()) } } /// Get the stable identifier of this instance. pub fn id(&self) -> Id { #[cfg(feature = "progress-tree-hp-hashmap")] { self.tree .get(&self.key) .map(|r| r.value().id) .unwrap_or(crate::progress::UNKNOWN) } #[cfg(not(feature = "progress-tree-hp-hashmap"))] { self.tree.get(&self.key, |v| v.id).unwrap_or(crate::progress::UNKNOWN) } } /// Returns the current step, as controlled by `inc*(…)` calls pub fn step(&self) -> Option { self.value.load(Ordering::Relaxed).into() } /// Returns the maximum about of items we expect, as provided with the `init(…)` call pub fn max(&self) -> Option { #[cfg(feature = "progress-tree-hp-hashmap")] { self.tree .get(&self.key) .and_then(|r| r.value().progress.as_ref().and_then(|p| p.done_at)) } #[cfg(not(feature = "progress-tree-hp-hashmap"))] { self.tree .get(&self.key, |v| v.progress.as_ref().and_then(|p| p.done_at)) .flatten() } } /// Set the maximum value to `max` and return the old maximum value. pub fn set_max(&mut self, max: Option) -> Option { #[cfg(feature = "progress-tree-hp-hashmap")] { self.tree .get_mut(&self.key)? .value_mut() .progress .as_mut() .and_then(|mut p| { let prev = p.done_at; p.done_at = max; prev }) } #[cfg(not(feature = "progress-tree-hp-hashmap"))] { self.tree .get_mut(&self.key, |v| { v.progress.as_mut().and_then(|mut p| { let prev = p.done_at; p.done_at = max; prev }) }) .flatten() } } /// Returns the (cloned) unit associated with this Progress pub fn unit(&self) -> Option { #[cfg(feature = "progress-tree-hp-hashmap")] { self.tree .get(&self.key) .and_then(|r| r.value().progress.as_ref().and_then(|p| p.unit.clone())) } #[cfg(not(feature = "progress-tree-hp-hashmap"))] { self.tree .get(&self.key, |v| v.progress.as_ref().and_then(|p| p.unit.clone())) .flatten() } } /// Set the current progress to the given `step`. /// /// **Note**: that this call has no effect unless `init(…)` was called before. pub fn set(&mut self, step: Step) { self.value.store(step, Ordering::SeqCst); } /// Increment the current progress by the given `step`. /// /// **Note**: that this call has no effect unless `init(…)` was called before. pub fn inc_by(&mut self, step: Step) { self.value.fetch_add(step, Ordering::SeqCst); } /// Increment the current progress by one. /// /// **Note**: that this call has no effect unless `init(…)` was called before. pub fn inc(&mut self) { self.value.fetch_add(1, Ordering::SeqCst); } /// Call to indicate that progress cannot be indicated, and that the task cannot be interrupted. /// Use this, as opposed to `halted(…)`, if a non-interruptable call is about to be made without support /// for any progress indication. /// /// If `eta` is `Some(…)`, it specifies the time at which this task is expected to /// make progress again. /// /// The halted-state is undone next time [`tree::Item::running(…)`][Item::running()] is called. pub fn blocked(&mut self, reason: &'static str, eta: Option) { self.alter_progress(|p| p.state = State::Blocked(reason, eta)); } /// Call to indicate that progress cannot be indicated, even though the task can be interrupted. /// Use this, as opposed to `blocked(…)`, if an interruptable call is about to be made without support /// for any progress indication. /// /// If `eta` is `Some(…)`, it specifies the time at which this task is expected to /// make progress again. /// /// The halted-state is undone next time [`tree::Item::running(…)`][Item::running()] is called. pub fn halted(&mut self, reason: &'static str, eta: Option) { self.alter_progress(|p| p.state = State::Halted(reason, eta)); } /// Call to indicate that progress is back in running state, which should be called after the reason for /// calling `blocked()` or `halted()` has passed. pub fn running(&mut self) { self.alter_progress(|p| p.state = State::Running); } /// Adds a new child `Tree`, whose parent is this instance, with the given `name`. /// /// **Important**: The depth of the hierarchy is limited to [`tree::Key::max_level`](./struct.Key.html#method.max_level). /// Exceeding the level will be ignored, and new tasks will be added to this instance's /// level instead. pub fn add_child(&mut self, name: impl Into) -> Item { self.add_child_with_id(name, crate::progress::UNKNOWN) } /// Adds a new child `Tree`, whose parent is this instance, with the given `name` and `id`. /// /// **Important**: The depth of the hierarchy is limited to [`tree::Key::max_level`](./struct.Key.html#method.max_level). /// Exceeding the level will be ignored, and new tasks will be added to this instance's /// level instead. pub fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Item { let child_key = self.key.add_child(self.highest_child_id); let task = Task { name: name.into(), id, progress: None, }; #[cfg(feature = "progress-tree-hp-hashmap")] self.tree.insert(child_key, task); #[cfg(not(feature = "progress-tree-hp-hashmap"))] self.tree.insert(child_key, task); self.highest_child_id = self.highest_child_id.wrapping_add(1); Item { highest_child_id: 0, value: Default::default(), key: child_key, tree: Arc::clone(&self.tree), messages: Arc::clone(&self.messages), } } /// Create a `message` of the given `level` and store it with the progress tree. /// /// Use this to provide additional,human-readable information about the progress /// made, including indicating success or failure. pub fn message(&self, level: MessageLevel, message: impl Into) { let message: String = message.into(); self.messages.lock().push_overwrite( level, { let name; #[cfg(feature = "progress-tree-hp-hashmap")] { name = self.tree.get(&self.key).map(|v| v.name.to_owned()).unwrap_or_default(); } #[cfg(not(feature = "progress-tree-hp-hashmap"))] { name = self.tree.get(&self.key, |v| v.name.to_owned()).unwrap_or_default() } #[cfg(feature = "progress-tree-log")] match level { MessageLevel::Failure => crate::warn!("{} → {}", name, message), MessageLevel::Info | MessageLevel::Success => crate::info!("{} → {}", name, message), }; name }, message, ) } /// Create a message indicating the task is done pub fn done(&mut self, message: impl Into) { self.message(MessageLevel::Success, message) } /// Create a message indicating the task failed pub fn fail(&mut self, message: impl Into) { self.message(MessageLevel::Failure, message) } /// Create a message providing additional information about the progress thus far. pub fn info(&mut self, message: impl Into) { self.message(MessageLevel::Info, message) } pub(crate) fn deep_clone(&self) -> Item { Item { key: self.key, value: Arc::new(AtomicUsize::new(self.value.load(Ordering::SeqCst))), highest_child_id: self.highest_child_id, tree: Arc::new(self.tree.deref().clone()), messages: Arc::new(Mutex::new(self.messages.lock().clone())), } } } impl crate::Progress for Item { type SubProgress = Item; fn add_child(&mut self, name: impl Into) -> Self::SubProgress { Item::add_child(self, name) } fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Self::SubProgress { Item::add_child_with_id(self, name, id) } fn init(&mut self, max: Option, unit: Option) { Item::init(self, max, unit) } fn set(&mut self, step: usize) { Item::set(self, step) } fn unit(&self) -> Option { Item::unit(self) } fn max(&self) -> Option { Item::max(self) } fn set_max(&mut self, max: Option) -> Option { Item::set_max(self, max) } fn step(&self) -> usize { Item::step(self).unwrap_or(0) } fn inc_by(&mut self, step: usize) { self.inc_by(step) } fn set_name(&mut self, name: impl Into) { Item::set_name(self, name) } fn name(&self) -> Option { Item::name(self) } fn id(&self) -> Id { Item::id(self) } fn message(&self, level: MessageLevel, message: impl Into) { Item::message(self, level, message) } fn counter(&self) -> Option { Some(Arc::clone(&self.value)) } }