diff options
Diffstat (limited to 'cli/util/progress_bar/mod.rs')
-rw-r--r-- | cli/util/progress_bar/mod.rs | 219 |
1 files changed, 198 insertions, 21 deletions
diff --git a/cli/util/progress_bar/mod.rs b/cli/util/progress_bar/mod.rs index 83292e2d1..8651e2d20 100644 --- a/cli/util/progress_bar/mod.rs +++ b/cli/util/progress_bar/mod.rs @@ -1,13 +1,23 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::time::SystemTime; + +use deno_core::parking_lot::Mutex; +use deno_runtime::ops::tty::ConsoleSize; + use crate::colors; -use self::draw_thread::DrawThread; -use self::draw_thread::ProgressBarEntry; +use self::renderer::ProgressBarRenderer; +use self::renderer::ProgressData; +use self::renderer::ProgressDataDisplayEntry; -use super::console::console_size; +use super::draw_thread::DrawThread; +use super::draw_thread::DrawThreadGuard; +use super::draw_thread::DrawThreadRenderer; -mod draw_thread; mod renderer; // Inspired by Indicatif, but this custom implementation allows @@ -46,29 +56,196 @@ pub enum ProgressBarStyle { } #[derive(Clone, Debug)] +struct ProgressBarEntry { + id: usize, + pub message: String, + pos: Arc<AtomicU64>, + total_size: Arc<AtomicU64>, + progress_bar: ProgressBarInner, +} + +impl ProgressBarEntry { + pub fn position(&self) -> u64 { + self.pos.load(Ordering::Relaxed) + } + + pub fn set_position(&self, new_pos: u64) { + self.pos.store(new_pos, Ordering::Relaxed); + } + + pub fn total_size(&self) -> u64 { + self.total_size.load(Ordering::Relaxed) + } + + pub fn set_total_size(&self, new_size: u64) { + self.total_size.store(new_size, Ordering::Relaxed); + } + + pub fn finish(&self) { + self.progress_bar.finish_entry(self.id); + } + + pub fn percent(&self) -> f64 { + let pos = self.pos.load(Ordering::Relaxed) as f64; + let total_size = self.total_size.load(Ordering::Relaxed) as f64; + if total_size == 0f64 { + 0f64 + } else { + pos / total_size + } + } +} + +#[derive(Debug)] +struct InternalState { + /// If this guard exists, then it means the progress + /// bar is displaying in the draw thread. + draw_thread_guard: Option<DrawThreadGuard>, + start_time: SystemTime, + keep_alive_count: usize, + total_entries: usize, + entries: Vec<ProgressBarEntry>, +} + +#[derive(Clone, Debug)] +struct ProgressBarInner { + state: Arc<Mutex<InternalState>>, + renderer: Arc<dyn ProgressBarRenderer>, +} + +impl ProgressBarInner { + fn new(renderer: Arc<dyn ProgressBarRenderer>) -> Self { + Self { + state: Arc::new(Mutex::new(InternalState { + draw_thread_guard: None, + start_time: SystemTime::now(), + keep_alive_count: 0, + total_entries: 0, + entries: Vec::new(), + })), + renderer, + } + } + + pub fn add_entry(&self, message: String) -> ProgressBarEntry { + let mut internal_state = self.state.lock(); + let id = internal_state.total_entries; + let entry = ProgressBarEntry { + id, + message, + pos: Default::default(), + total_size: Default::default(), + progress_bar: self.clone(), + }; + internal_state.entries.push(entry.clone()); + internal_state.total_entries += 1; + internal_state.keep_alive_count += 1; + + self.maybe_start_draw_thread(&mut internal_state); + + entry + } + + fn finish_entry(&self, entry_id: usize) { + let mut internal_state = self.state.lock(); + + if let Ok(index) = internal_state + .entries + .binary_search_by(|e| e.id.cmp(&entry_id)) + { + internal_state.entries.remove(index); + self.decrement_keep_alive(&mut internal_state); + } + } + + pub fn increment_clear(&self) { + let mut internal_state = self.state.lock(); + internal_state.keep_alive_count += 1; + } + + pub fn decrement_clear(&self) { + let mut internal_state = self.state.lock(); + self.decrement_keep_alive(&mut internal_state); + } + + fn decrement_keep_alive(&self, state: &mut InternalState) { + state.keep_alive_count -= 1; + + if state.keep_alive_count == 0 { + // drop the guard to remove this from the draw thread + state.draw_thread_guard.take(); + } + } + + fn maybe_start_draw_thread(&self, internal_state: &mut InternalState) { + if internal_state.draw_thread_guard.is_none() + && internal_state.keep_alive_count > 0 + { + internal_state.start_time = SystemTime::now(); + internal_state.draw_thread_guard = + Some(DrawThread::add_entry(0, Arc::new(self.clone()))); + } + } +} + +impl DrawThreadRenderer for ProgressBarInner { + fn render(&self, size: &ConsoleSize) -> String { + let data = { + let state = self.state.lock(); + if state.entries.is_empty() { + return String::new(); + } + let preferred_entry = state + .entries + .iter() + .find(|e| e.percent() > 0f64) + .or_else(|| state.entries.iter().last()) + .unwrap(); + ProgressData { + duration: state.start_time.elapsed().unwrap(), + terminal_width: size.cols, + pending_entries: state.entries.len(), + total_entries: state.total_entries, + display_entry: ProgressDataDisplayEntry { + message: preferred_entry.message.clone(), + position: preferred_entry.position(), + total_size: preferred_entry.total_size(), + }, + percent_done: { + let mut total_percent_sum = 0f64; + for entry in &state.entries { + total_percent_sum += entry.percent(); + } + total_percent_sum += + (state.total_entries - state.entries.len()) as f64; + total_percent_sum / (state.total_entries as f64) + }, + } + }; + self.renderer.render(data) + } +} + +#[derive(Clone, Debug)] pub struct ProgressBar { - draw_thread: Option<DrawThread>, + inner: Option<ProgressBarInner>, } impl ProgressBar { /// Checks if progress bars are supported pub fn are_supported() -> bool { - atty::is(atty::Stream::Stderr) - && log::log_enabled!(log::Level::Info) - && console_size() - .map(|s| s.cols > 0 && s.rows > 0) - .unwrap_or(false) + DrawThread::is_supported() } pub fn new(style: ProgressBarStyle) -> Self { Self { - draw_thread: match Self::are_supported() { - true => Some(DrawThread::new(match style { + inner: match Self::are_supported() { + true => Some(ProgressBarInner::new(match style { ProgressBarStyle::DownloadBars => { - Box::new(renderer::BarProgressBarRenderer) + Arc::new(renderer::BarProgressBarRenderer) } ProgressBarStyle::TextOnly => { - Box::new(renderer::TextOnlyProgressBarRenderer) + Arc::new(renderer::TextOnlyProgressBarRenderer) } })), false => None, @@ -77,9 +254,9 @@ impl ProgressBar { } pub fn update(&self, msg: &str) -> UpdateGuard { - match &self.draw_thread { - Some(draw_thread) => { - let entry = draw_thread.add_entry(msg.to_string()); + match &self.inner { + Some(inner) => { + let entry = inner.add_entry(msg.to_string()); UpdateGuard { maybe_entry: Some(entry), } @@ -95,15 +272,15 @@ impl ProgressBar { } pub fn clear_guard(&self) -> ClearGuard { - if let Some(draw_thread) = &self.draw_thread { - draw_thread.increment_clear(); + if let Some(inner) = &self.inner { + inner.increment_clear(); } ClearGuard { pb: self.clone() } } fn decrement_clear(&self) { - if let Some(draw_thread) = &self.draw_thread { - draw_thread.decrement_clear(); + if let Some(inner) = &self.inner { + inner.decrement_clear(); } } } |