use super::ProgressReporter;
use crate::indicator::{ProgressConfig, ProgressFactory, ProgressIndicator, ProgressStyle};
use crate::storage::formatting::format_size;
const CHILD_PROGRESS_THRESHOLD: u64 = 10 * 1024 * 1024;
pub struct DownloadProgressAdapter {
indicator: Box<dyn ProgressIndicator>,
parent_indicator: Option<Box<dyn ProgressIndicator>>,
child_indicator: Option<Box<dyn ProgressIndicator>>,
}
impl DownloadProgressAdapter {
pub fn new(parent: Option<Box<dyn ProgressIndicator>>, no_progress: bool) -> Self {
Self {
indicator: if parent.is_none() {
ProgressFactory::create(no_progress)
} else {
ProgressFactory::create(false) },
parent_indicator: parent,
child_indicator: None,
}
}
pub fn for_jdk_download(
_package_name: &str,
parent: Option<Box<dyn ProgressIndicator>>,
no_progress: bool,
) -> Self {
Self::new(parent, no_progress)
}
}
impl ProgressReporter for DownloadProgressAdapter {
fn on_start(&mut self, total_bytes: u64) {
if let Some(parent) = self.parent_indicator.take() {
if total_bytes >= CHILD_PROGRESS_THRESHOLD {
self.parent_indicator = Some(parent);
let mut child = self.parent_indicator.as_mut().unwrap().create_child();
let config = ProgressConfig::new(ProgressStyle::Bytes).with_total(total_bytes);
child.start(config);
if total_bytes > 0 {
child.set_message(format!("0 / {total_bytes} bytes"));
} else {
child.set_message("Starting download...".to_string());
}
self.child_indicator = Some(child);
} else {
self.parent_indicator = Some(parent);
let msg = if total_bytes == 0 {
"Downloading (unknown size)".to_string()
} else {
format!("Downloading ({})", format_size(total_bytes))
};
self.parent_indicator.as_mut().unwrap().set_message(msg);
}
} else {
let config = if total_bytes > 0 {
ProgressConfig::new(ProgressStyle::Bytes).with_total(total_bytes)
} else {
ProgressConfig::new(ProgressStyle::Bytes)
};
self.indicator.start(config);
if total_bytes > 0 {
self.indicator
.set_message(format!("0 / {total_bytes} bytes"));
} else {
self.indicator
.set_message("Starting download...".to_string());
}
}
}
fn on_progress(&mut self, bytes_downloaded: u64) {
if let Some(child) = &mut self.child_indicator {
child.update(bytes_downloaded, None);
let mb_downloaded = bytes_downloaded as f64 / (1024.0 * 1024.0);
child.set_message(format!("{mb_downloaded:.1} MB downloaded"));
} else if self.parent_indicator.is_some() {
let mb_downloaded = bytes_downloaded as f64 / (1024.0 * 1024.0);
let msg = format!("Downloading ({mb_downloaded:.1} MB)");
self.parent_indicator.as_mut().unwrap().set_message(msg);
} else {
self.indicator.update(bytes_downloaded, None);
let mb_downloaded = bytes_downloaded as f64 / (1024.0 * 1024.0);
self.indicator
.set_message(format!("{mb_downloaded:.1} MB downloaded"));
}
}
fn on_complete(&mut self) {
if let Some(mut child) = self.child_indicator.take() {
child.complete(Some("Download complete".to_string()));
} else if self.parent_indicator.is_some() {
let msg = "Downloaded".to_string();
self.parent_indicator.as_mut().unwrap().set_message(msg);
} else {
self.indicator
.complete(Some("Download complete".to_string()));
}
}
}
pub type IndicatifProgressReporter = DownloadProgressAdapter;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_download_progress_with_total() {
let mut adapter = DownloadProgressAdapter::for_jdk_download("temurin@21", None, false);
adapter.on_start(1024 * 1024);
adapter.on_progress(512 * 1024); adapter.on_progress(1024 * 1024);
adapter.on_complete();
}
#[test]
fn test_download_progress_without_total() {
let mut adapter = DownloadProgressAdapter::for_jdk_download("liberica@17", None, false);
adapter.on_start(0);
adapter.on_progress(256 * 1024); adapter.on_progress(512 * 1024);
adapter.on_complete();
}
#[test]
fn test_download_progress_no_progress_mode() {
let mut adapter = DownloadProgressAdapter::for_jdk_download("corretto@11", None, true);
adapter.on_start(2048 * 1024); adapter.on_progress(1024 * 1024); adapter.on_complete();
}
#[test]
fn test_custom_operation_context() {
let mut adapter = DownloadProgressAdapter::new(None, false);
adapter.on_start(5000000);
adapter.on_progress(2500000);
adapter.on_complete();
}
#[test]
fn test_progress_reporter_trait_impl() {
fn accepts_reporter(_reporter: Box<dyn ProgressReporter>) {}
let adapter = DownloadProgressAdapter::for_jdk_download("zulu@21", None, false);
accepts_reporter(Box::new(adapter));
}
#[test]
fn test_incremental_progress_updates() {
let mut adapter = DownloadProgressAdapter::for_jdk_download("graalvm@21", None, false);
adapter.on_start(1000);
for i in 1..=10 {
adapter.on_progress(i * 100);
}
adapter.on_complete();
}
#[test]
fn test_large_download_creates_child() {
let parent = ProgressFactory::create(false);
let mut adapter =
DownloadProgressAdapter::for_jdk_download("temurin@21", Some(parent), false);
adapter.on_start(15 * 1024 * 1024);
adapter.on_progress(5 * 1024 * 1024); adapter.on_progress(10 * 1024 * 1024); adapter.on_progress(15 * 1024 * 1024);
adapter.on_complete();
}
#[test]
fn test_small_download_no_child() {
let parent = ProgressFactory::create(false);
let mut adapter =
DownloadProgressAdapter::for_jdk_download("tool@1.0", Some(parent), false);
adapter.on_start(5 * 1024 * 1024);
adapter.on_progress(1024 * 1024); adapter.on_progress(3 * 1024 * 1024); adapter.on_progress(5 * 1024 * 1024);
adapter.on_complete();
}
#[test]
fn test_unknown_size_no_child() {
let parent = ProgressFactory::create(false);
let mut adapter =
DownloadProgressAdapter::for_jdk_download("unknown@1.0", Some(parent), false);
adapter.on_start(0);
adapter.on_progress(100_000);
adapter.on_progress(200_000);
adapter.on_progress(300_000);
adapter.on_complete();
}
#[test]
fn test_exact_threshold_creates_child() {
let parent = ProgressFactory::create(false);
let mut adapter =
DownloadProgressAdapter::for_jdk_download("boundary@1.0", Some(parent), false);
adapter.on_start(10 * 1024 * 1024);
adapter.on_progress(5 * 1024 * 1024);
adapter.on_progress(10 * 1024 * 1024);
adapter.on_complete();
}
#[test]
fn test_just_below_threshold_no_child() {
let parent = ProgressFactory::create(false);
let mut adapter =
DownloadProgressAdapter::for_jdk_download("small@1.0", Some(parent), false);
adapter.on_start(10 * 1024 * 1024 - 1);
adapter.on_progress(5 * 1024 * 1024);
adapter.on_progress(10 * 1024 * 1024 - 1);
adapter.on_complete();
}
}