use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use super::renderer::ProgressRenderer;
use super::statistics::ProgressStats;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ProgressStyle {
Percentage,
Bar,
BlockBar,
Spinner,
DetailedBar,
}
#[derive(Debug, Clone)]
pub struct ProgressConfig {
pub style: ProgressStyle,
pub width: usize,
pub show_eta: bool,
pub show_statistics: bool,
pub show_speed: bool,
pub adaptive_rate: bool,
pub min_update_interval: Duration,
pub max_update_interval: Duration,
pub template: Option<String>,
pub symbols: Option<ProgressSymbols>,
}
impl Default for ProgressConfig {
fn default() -> Self {
Self {
style: ProgressStyle::BlockBar,
width: 40,
show_eta: true,
show_statistics: true,
show_speed: true,
adaptive_rate: true,
min_update_interval: Duration::from_millis(100),
max_update_interval: Duration::from_secs(1),
template: None,
symbols: None,
}
}
}
#[derive(Debug, Clone)]
pub struct ProgressSymbols {
pub start: String,
pub end: String,
pub fill: String,
pub empty: String,
pub spinner: Vec<String>,
}
impl Default for ProgressSymbols {
fn default() -> Self {
Self {
start: "[".to_string(),
end: "]".to_string(),
fill: "=".to_string(),
empty: " ".to_string(),
spinner: vec![
"-".to_string(),
"\\".to_string(),
"|".to_string(),
"/".to_string(),
],
}
}
}
impl ProgressSymbols {
pub fn blocks() -> Self {
Self {
start: "│".to_string(),
end: "│".to_string(),
fill: "█".to_string(),
empty: " ".to_string(),
spinner: vec![
"⠋".to_string(),
"⠙".to_string(),
"⠹".to_string(),
"⠸".to_string(),
"⠼".to_string(),
"⠴".to_string(),
"⠦".to_string(),
"⠧".to_string(),
"⠇".to_string(),
"⠏".to_string(),
],
}
}
}
pub struct EnhancedProgressTracker {
pub description: String,
pub config: ProgressConfig,
stats: Arc<Mutex<ProgressStats>>,
start_time: Instant,
active: bool,
hidden: bool,
renderer: ProgressRenderer,
}
impl EnhancedProgressTracker {
pub fn new(description: &str, total: u64) -> Self {
let config = ProgressConfig::default();
let stats = Arc::new(Mutex::new(ProgressStats::new(total)));
let renderer = ProgressRenderer::new();
Self {
description: description.to_string(),
config,
stats,
start_time: Instant::now(),
active: false,
hidden: false,
renderer,
}
}
pub fn with_config(mut self, config: ProgressConfig) -> Self {
self.config = config;
self
}
pub const fn with_style(mut self, style: ProgressStyle) -> Self {
self.config.style = style;
self
}
pub fn with_symbols(mut self, symbols: ProgressSymbols) -> Self {
self.config.symbols = Some(symbols);
self
}
pub const fn with_eta(mut self, show: bool) -> Self {
self.config.show_eta = show;
self
}
pub const fn with_statistics(mut self, show: bool) -> Self {
self.config.show_statistics = show;
self
}
pub fn start(&mut self) {
self.active = true;
self.start_time = Instant::now();
if !self.hidden {
self.renderer.init();
self.render();
}
}
pub fn update(&mut self, processed: u64) {
if !self.active {
return;
}
let now = Instant::now();
{
let mut stats = self.stats.lock().expect("Operation failed");
stats.update(processed, now);
}
let should_render = if self.config.adaptive_rate {
self.should_update_adaptive()
} else {
self.should_update_fixed()
};
if should_render && !self.hidden {
self.render();
}
}
pub fn increment(&mut self, amount: u64) {
if !self.active {
return;
}
let processed = {
let stats = self.stats.lock().expect("Operation failed");
stats.processed + amount
};
self.update(processed);
}
pub fn finish(&mut self) {
if !self.active {
return;
}
self.active = false;
{
let mut stats = self.stats.lock().expect("Operation failed");
let total = stats.total;
stats.processed = total;
stats.percentage = 100.0;
stats.eta = Duration::from_secs(0);
stats.update(total, Instant::now());
}
if !self.hidden {
self.render();
self.renderer.finalize();
}
}
pub fn hide(&mut self) {
self.hidden = true;
}
pub fn show(&mut self) {
self.hidden = false;
if self.active {
self.render();
}
}
pub fn stats(&self) -> ProgressStats {
self.stats.lock().expect("Operation failed").clone()
}
fn should_update_fixed(&self) -> bool {
let stats = self.stats.lock().expect("Operation failed");
let elapsed = stats.last_update.elapsed();
elapsed >= self.config.min_update_interval
}
fn should_update_adaptive(&self) -> bool {
let stats = self.stats.lock().expect("Operation failed");
let elapsed = stats.last_update.elapsed();
if elapsed >= self.config.max_update_interval {
return true;
}
if elapsed >= self.config.min_update_interval {
let progress_ratio = if stats.total > 0 {
stats.processed as f64 / stats.total as f64
} else {
0.0
};
let position_factor = 4.0 * progress_ratio * (1.0 - progress_ratio);
let threshold = self.config.min_update_interval.as_secs_f64()
+ position_factor
* (self.config.max_update_interval.as_secs_f64()
- self.config.min_update_interval.as_secs_f64());
elapsed.as_secs_f64() >= threshold
} else {
false
}
}
fn render(&mut self) {
if self.hidden {
return;
}
let stats = self.stats.lock().expect("Operation failed");
let symbols = self.config.symbols.clone().unwrap_or_default();
match self.config.style {
ProgressStyle::Percentage => {
self.renderer.renderpercentage(&self.description, &stats);
}
ProgressStyle::Bar => {
self.renderer.render_basic(
&self.description,
&stats,
self.config.width,
self.config.show_eta,
&symbols,
);
}
ProgressStyle::BlockBar => {
let block_symbols = ProgressSymbols::blocks();
self.renderer.render_basic(
&self.description,
&stats,
self.config.width,
self.config.show_eta,
&block_symbols,
);
}
ProgressStyle::Spinner => {
self.renderer.render_spinner(
&self.description,
&stats,
self.config.show_eta,
&symbols,
);
}
ProgressStyle::DetailedBar => {
self.renderer.render_detailed(
&self.description,
&stats,
self.config.width,
self.config.show_eta,
self.config.show_statistics,
self.config.show_speed,
&symbols,
);
}
}
}
}
pub struct ProgressBuilder {
description: String,
total: u64,
style: ProgressStyle,
width: usize,
show_eta: bool,
show_statistics: bool,
show_speed: bool,
adaptive_rate: bool,
min_update_interval: Duration,
max_update_interval: Duration,
template: Option<String>,
symbols: Option<ProgressSymbols>,
hidden: bool,
}
impl ProgressBuilder {
pub fn new(description: &str, total: u64) -> Self {
Self {
description: description.to_string(),
total,
style: ProgressStyle::BlockBar,
width: 40,
show_eta: true,
show_statistics: true,
show_speed: true,
adaptive_rate: true,
min_update_interval: Duration::from_millis(100),
max_update_interval: Duration::from_secs(1),
template: None,
symbols: None,
hidden: false,
}
}
pub const fn style(mut self, style: ProgressStyle) -> Self {
self.style = style;
self
}
pub const fn width(mut self, width: usize) -> Self {
self.width = width;
self
}
pub const fn show_eta(mut self, show: bool) -> Self {
self.show_eta = show;
self
}
pub const fn show_statistics(mut self, show: bool) -> Self {
self.show_statistics = show;
self
}
pub const fn show_speed(mut self, show: bool) -> Self {
self.show_speed = show;
self
}
pub const fn adaptive_rate(mut self, enable: bool) -> Self {
self.adaptive_rate = enable;
self
}
pub const fn min_update_interval(mut self, interval: Duration) -> Self {
self.min_update_interval = interval;
self
}
pub const fn max_update_interval(mut self, interval: Duration) -> Self {
self.max_update_interval = interval;
self
}
pub fn template(mut self, template: &str) -> Self {
self.template = Some(template.to_string());
self
}
pub fn symbols(mut self, symbols: ProgressSymbols) -> Self {
self.symbols = Some(symbols);
self
}
pub const fn hidden(mut self, hidden: bool) -> Self {
self.hidden = hidden;
self
}
pub fn build(self) -> EnhancedProgressTracker {
let config = ProgressConfig {
style: self.style,
width: self.width,
show_eta: self.show_eta,
show_statistics: self.show_statistics,
show_speed: self.show_speed,
adaptive_rate: self.adaptive_rate,
min_update_interval: self.min_update_interval,
max_update_interval: self.max_update_interval,
template: self.template,
symbols: self.symbols,
};
let mut tracker =
EnhancedProgressTracker::new(&self.description, self.total).with_config(config);
if self.hidden {
tracker.hide();
}
tracker
}
}