use crate::Result;
use std::sync::Arc;
#[async_trait::async_trait]
pub trait ProgressReporter: Send + Sync {
async fn start(&self, message: &str, total: Option<u64>);
async fn update(&self, position: u64, message: Option<&str>);
async fn increment(&self, delta: u64);
async fn finish(&self, message: &str);
async fn finish_with_error(&self, message: &str);
async fn set_total(&self, total: u64);
}
#[derive(Debug, Clone)]
pub struct ProgressStyle {
pub template: String,
pub progress_chars: String,
pub show_elapsed: bool,
pub show_eta: bool,
pub show_rate: bool,
}
impl Default for ProgressStyle {
fn default() -> Self {
Self {
template: "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})".to_string(),
progress_chars: "#>-".to_string(),
show_elapsed: true,
show_eta: true,
show_rate: true,
}
}
}
impl ProgressStyle {
pub fn simple() -> Self {
Self {
template: "{wide_bar} {pos}/{len}".to_string(),
progress_chars: "=>-".to_string(),
show_elapsed: false,
show_eta: false,
show_rate: false,
}
}
pub fn detailed() -> Self {
Self::default()
}
pub fn minimal() -> Self {
Self {
template: "{spinner} {msg}".to_string(),
progress_chars: "⠁⠂⠄⡀⢀⠠⠐⠈".to_string(),
show_elapsed: false,
show_eta: false,
show_rate: false,
}
}
}
#[cfg(feature = "progress")]
pub struct ConsoleProgressReporter {
bar: std::sync::Mutex<Option<indicatif::ProgressBar>>,
style: ProgressStyle,
}
#[cfg(feature = "progress")]
impl ConsoleProgressReporter {
pub fn new(style: ProgressStyle) -> Self {
Self {
bar: std::sync::Mutex::new(None),
style,
}
}
pub fn default() -> Self {
Self::new(ProgressStyle::default())
}
}
#[cfg(feature = "progress")]
#[async_trait::async_trait]
impl ProgressReporter for ConsoleProgressReporter {
async fn start(&self, message: &str, total: Option<u64>) {
use indicatif::{ProgressBar, ProgressStyle as IndicatifStyle};
let bar = if let Some(total) = total {
ProgressBar::new(total)
} else {
ProgressBar::new_spinner()
};
let style = IndicatifStyle::with_template(&self.style.template)
.unwrap_or_else(|_| IndicatifStyle::default_bar())
.progress_chars(&self.style.progress_chars);
bar.set_style(style);
bar.set_message(message.to_string());
if let Ok(mut bar_guard) = self.bar.lock() {
*bar_guard = Some(bar);
}
}
async fn update(&self, position: u64, message: Option<&str>) {
if let Ok(bar_guard) = self.bar.lock() {
if let Some(ref bar) = *bar_guard {
bar.set_position(position);
if let Some(msg) = message {
bar.set_message(msg.to_string());
}
}
}
}
async fn increment(&self, delta: u64) {
if let Ok(bar_guard) = self.bar.lock() {
if let Some(ref bar) = *bar_guard {
bar.inc(delta);
}
}
}
async fn finish(&self, message: &str) {
if let Ok(mut bar_guard) = self.bar.lock() {
if let Some(bar) = bar_guard.take() {
bar.finish_with_message(message.to_string());
}
}
}
async fn finish_with_error(&self, message: &str) {
if let Ok(mut bar_guard) = self.bar.lock() {
if let Some(bar) = bar_guard.take() {
bar.finish_with_message(format!("❌ {}", message));
}
}
}
async fn set_total(&self, total: u64) {
if let Ok(bar_guard) = self.bar.lock() {
if let Some(ref bar) = *bar_guard {
bar.set_length(total);
}
}
}
}
pub struct NoOpProgressReporter;
#[async_trait::async_trait]
impl ProgressReporter for NoOpProgressReporter {
async fn start(&self, _message: &str, _total: Option<u64>) {}
async fn update(&self, _position: u64, _message: Option<&str>) {}
async fn increment(&self, _delta: u64) {}
async fn finish(&self, _message: &str) {}
async fn finish_with_error(&self, _message: &str) {}
async fn set_total(&self, _total: u64) {}
}
pub fn create_progress_reporter(style: ProgressStyle, enabled: bool) -> Arc<dyn ProgressReporter> {
if enabled {
#[cfg(feature = "progress")]
{
Arc::new(ConsoleProgressReporter::new(style))
}
#[cfg(not(feature = "progress"))]
{
let _ = style;
Arc::new(NoOpProgressReporter)
}
} else {
Arc::new(NoOpProgressReporter)
}
}
pub struct ProgressContext {
reporter: Arc<dyn ProgressReporter>,
enabled: bool,
}
impl ProgressContext {
pub fn new(reporter: Arc<dyn ProgressReporter>, enabled: bool) -> Self {
Self { reporter, enabled }
}
pub fn disabled() -> Self {
Self {
reporter: Arc::new(NoOpProgressReporter),
enabled: false,
}
}
pub fn reporter(&self) -> &Arc<dyn ProgressReporter> {
&self.reporter
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub async fn start(&self, message: &str, total: Option<u64>) -> Result<()> {
if self.enabled {
self.reporter.start(message, total).await;
}
Ok(())
}
pub async fn update(&self, position: u64, message: Option<&str>) -> Result<()> {
if self.enabled {
self.reporter.update(position, message).await;
}
Ok(())
}
pub async fn increment(&self, delta: u64) -> Result<()> {
if self.enabled {
self.reporter.increment(delta).await;
}
Ok(())
}
pub async fn finish(&self, message: &str) -> Result<()> {
if self.enabled {
self.reporter.finish(message).await;
}
Ok(())
}
}