use std::sync::Arc;
use std::time::{Duration, Instant};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProgressPhase {
Upload,
Download,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Progress {
phase: ProgressPhase,
transferred: usize,
total: Option<usize>,
done: bool,
}
impl Progress {
pub fn new(phase: ProgressPhase, transferred: usize, total: Option<usize>, done: bool) -> Self {
Self {
phase,
transferred,
total,
done,
}
}
pub fn phase(&self) -> ProgressPhase {
self.phase
}
pub fn transferred(&self) -> usize {
self.transferred
}
pub fn total(&self) -> Option<usize> {
self.total
}
pub fn is_done(&self) -> bool {
self.done
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProgressConfig {
min_interval: Duration,
min_bytes: usize,
}
impl Default for ProgressConfig {
fn default() -> Self {
Self {
min_interval: DEFAULT_PROGRESS_INTERVAL,
min_bytes: DEFAULT_PROGRESS_BYTES,
}
}
}
impl ProgressConfig {
pub fn new(min_interval: Duration, min_bytes: usize) -> Self {
Self {
min_interval,
min_bytes,
}
}
pub fn min_interval(&self) -> Duration {
self.min_interval
}
pub fn min_bytes(&self) -> usize {
self.min_bytes
}
}
pub(crate) const DEFAULT_PROGRESS_INTERVAL: Duration = Duration::from_millis(100);
pub(crate) const DEFAULT_PROGRESS_BYTES: usize = 64 * 1024;
pub(crate) struct ProgressReporter {
callback: Arc<dyn Fn(Progress) + Send + Sync + 'static>,
config: ProgressConfig,
phase: ProgressPhase,
total: Option<usize>,
transferred: usize,
last_emitted_at: Option<Instant>,
last_emitted_bytes: usize,
}
impl ProgressReporter {
pub(crate) fn new(
callback: Arc<dyn Fn(Progress) + Send + Sync + 'static>,
phase: ProgressPhase,
total: Option<usize>,
config: ProgressConfig,
) -> Self {
Self {
callback,
config,
phase,
total,
transferred: 0,
last_emitted_at: None,
last_emitted_bytes: 0,
}
}
pub(crate) fn record(&mut self, delta: usize) {
if delta == 0 {
return;
}
self.transferred += delta;
if self.should_emit() {
self.emit(false);
}
}
pub(crate) fn finish(&mut self) {
self.emit(true);
}
fn should_emit(&self) -> bool {
if self.transferred.saturating_sub(self.last_emitted_bytes) >= self.config.min_bytes {
return true;
}
match self.last_emitted_at {
Some(last) => last.elapsed() >= self.config.min_interval,
None => true,
}
}
fn emit(&mut self, done: bool) {
self.last_emitted_at = Some(Instant::now());
self.last_emitted_bytes = self.transferred;
(self.callback)(Progress::new(
self.phase,
self.transferred,
self.total,
done,
));
}
}