diff options
| author | 2025-03-15 19:59:11 -0500 | |
|---|---|---|
| committer | 2025-03-15 19:59:11 -0500 | |
| commit | 7bd8a5624e0f6340af8bb930ee98b3386114705b (patch) | |
| tree | 2c7c7948a43839f4f00f557b4836db2176a352da | |
| parent | wip: progress (diff) | |
granular progress indication
the code for the cli kinda sucks
| -rw-r--r-- | Cargo.lock | 11 | ||||
| -rw-r--r-- | ozone-cli/Cargo.toml | 1 | ||||
| -rw-r--r-- | ozone-cli/src/main.rs | 16 | ||||
| -rw-r--r-- | ozone/src/launcher.rs | 19 | ||||
| -rw-r--r-- | ozone/src/launcher/assets.rs | 11 | ||||
| -rw-r--r-- | ozone/src/launcher/download.rs | 51 | ||||
| -rw-r--r-- | ozone/src/launcher/jre.rs | 41 | ||||
| -rw-r--r-- | ozone/src/launcher/jre/download.rs | 23 | ||||
| -rw-r--r-- | ozone/src/util/progress.rs | 102 |
9 files changed, 175 insertions, 100 deletions
@@ -2622,6 +2622,16 @@ dependencies = [ ] [[package]] +name = "indicatif-log-bridge" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63703cf9069b85dbe6fe26e1c5230d013dee99d3559cd3d02ba39e099ef7ab02" +dependencies = [ + "indicatif", + "log", +] + +[[package]] name = "input" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3577,6 +3587,7 @@ dependencies = [ "clap", "clap_complete", "indicatif", + "indicatif-log-bridge", "lazy_static", "log", "ozone", diff --git a/ozone-cli/Cargo.toml b/ozone-cli/Cargo.toml index ad65d92..77d93d0 100644 --- a/ozone-cli/Cargo.toml +++ b/ozone-cli/Cargo.toml @@ -14,3 +14,4 @@ lazy_static = "1.5.0" regex = "1.11.1" uuid = { version = "1.12.1", features = ["v4"] } indicatif = "0.17.11" +indicatif-log-bridge = "0.2.3" diff --git a/ozone-cli/src/main.rs b/ozone-cli/src/main.rs index dda2c32..05fef49 100644 --- a/ozone-cli/src/main.rs +++ b/ozone-cli/src/main.rs @@ -8,6 +8,7 @@ use std::time::Duration; use log::{error, info, trace}; use clap::Parser; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use indicatif_log_bridge::LogWrapper; use ozone::launcher::{Instance, JavaRuntimeSetting, LaunchProgress, Launcher, ProgressIndication, Settings, ALT_CLIENT_ID, MAIN_CLIENT_ID}; use ozone::launcher::version::{VersionList, VersionResult}; use uuid::Uuid; @@ -156,7 +157,7 @@ fn display_account(account: &Account, selected: bool, verbose: bool) { } } -async fn main_inner(cli: Cli) -> Result<ExitCode, Box<dyn Error>> { +async fn main_inner(cli: Cli, multi: MultiProgress) -> Result<ExitCode, Box<dyn Error>> { let Some(home) = cli.home.or_else(Launcher::sensible_home) else { error!("Could not choose a launcher home directory. Please choose one with `--home'."); return Ok(ExitCode::FAILURE); // we print our own error message @@ -515,17 +516,18 @@ async fn main_inner(cli: Cli) -> Result<ExitCode, Box<dyn Error>> { println!("Preparing the game files..."); let launcher = Launcher::new(&home, !cli.offline).await?; - let multi = MultiProgress::new(); let progress = IndicatifProgress { bar: multi.add(ProgressBar::no_length() .with_style(ProgressStyle::with_template("{spinner} Step {pos} of {len}: {msg}").unwrap())) }; let progress_sub = IndicatifProgress { - bar: multi.add(ProgressBar::no_length()) + bar: multi.add(ProgressBar::no_length() + .with_style(ProgressStyle::with_template("{bar} {pos}/{len}: {msg}").unwrap())) }; progress.bar.enable_steady_tick(Duration::from_millis(100)); + progress_sub.bar.enable_steady_tick(Duration::from_millis(50)); let lp = LaunchProgress::new(&progress as &dyn ProgressIndication, &progress_sub as &dyn ProgressIndication); @@ -541,6 +543,9 @@ async fn main_inner(cli: Cli) -> Result<ExitCode, Box<dyn Error>> { info!("ok!"); + progress.bar.finish(); + progress_sub.bar.finish(); + multi.clear().unwrap(); println!("Launching the game!"); ozone::launcher::run_the_game(&launch)?; @@ -553,11 +558,12 @@ async fn main_inner(cli: Cli) -> Result<ExitCode, Box<dyn Error>> { #[tokio::main] async fn main() -> ExitCode { // use Warn as the default level to minimize noise on the command line - //simple_logger::SimpleLogger::new().env().init().unwrap(); + let progress = MultiProgress::new(); + LogWrapper::new(progress.clone(), simple_logger::SimpleLogger::new().env()).try_init().unwrap(); let arg = Cli::parse(); - main_inner(arg).await.unwrap_or_else(|e| { + main_inner(arg, progress).await.unwrap_or_else(|e| { error!("Launcher initialization error:"); error!("{e}"); diff --git a/ozone/src/launcher.rs b/ozone/src/launcher.rs index b21f873..41abd3c 100644 --- a/ozone/src/launcher.rs +++ b/ozone/src/launcher.rs @@ -339,7 +339,7 @@ impl Launcher { /* TODO: * - launch game using JNI */ - pub async fn prepare_launch<'prog>(&self, instance: &Instance, inst_home: impl AsRef<Path>, client_id: Uuid, account: &Account, force_demo: bool, progress: LaunchProgress<'prog>) -> Result<Launch, LaunchError> { + pub async fn prepare_launch(&self, instance: &Instance, inst_home: impl AsRef<Path>, client_id: Uuid, account: &Account, force_demo: bool, progress: LaunchProgress<'_>) -> Result<Launch, LaunchError> { let steps: StepProgress = progress.indicator.into(); let step_init = steps.step("Resolve game version"); let step_prep_libraries = steps.step("Collect required libraries"); @@ -432,7 +432,7 @@ impl Launcher { .map_err(|_| LaunchError::LibraryDownloadError)?; } else { info!("Verifying {} libraries...", downloads.len()); - download::verify_files::<dyn FileDownload, _>(downloads.values()).await.map_err(|e| { + download::verify_files::<dyn FileDownload, _>(downloads.values(), progress.indicator_sub).await.map_err(|e| { warn!("A library could not be verified: {}", e); warn!("Since the launcher is in offline mode, libraries cannot be downloaded. Please try again in online mode."); LaunchError::LibraryVerifyError(e) @@ -457,7 +457,7 @@ impl Launcher { let asset_idx = self.assets.load_index(idx_download, asset_idx_name).await .map_err(LaunchError::Assets)?; - self.assets.ensure_assets(&asset_idx).await.map_err(LaunchError::Assets)?; + self.assets.ensure_assets(&asset_idx, progress.indicator_sub).await.map_err(LaunchError::Assets)?; (asset_idx_name, Some(asset_idx)) } else { @@ -532,7 +532,7 @@ impl Launcher { }; let runtime = self.java_runtimes.choose_runtime(component).await.map_err(LaunchError::JavaRuntimeRepo)?; - runtime_path = self.java_runtimes.ensure_jre(component, runtime).await.map_err(LaunchError::JavaRuntimeRepo)?; + runtime_path = self.java_runtimes.ensure_jre(component, runtime, progress.indicator_sub).await.map_err(LaunchError::JavaRuntimeRepo)?; } let Some(runtime_exe_path) = runner::find_java(runtime_path.as_path(), instance.jni_launch).await @@ -637,15 +637,20 @@ impl LibraryRepository { } fn create_download(&self, lib: &Library, classifier: Option<&str>) -> Option<Box<dyn FileDownload>> { + let path_display = |path: &Path| { + path.components().last().as_ref().map_or(OsStr::new("???"), |c| c.as_ref()).to_string_lossy().into_owned() + }; + let flat_library = |base| { let path = LibraryRepository::get_artifact_path(lib.name.as_str(), classifier)?; let url: String = [base, path.to_string_lossy().as_ref()].into_iter().collect(); + let filename = path_display(&path); if let Some(digest) = lib.sha1 { - Some(Box::new(VerifiedDownload::new(url, self.home.join(path), lib.size, Some(digest))) as Box<dyn FileDownload>) + Some(Box::new(VerifiedDownload::new(url, self.home.join(path), lib.size, Some(digest), filename)) as Box<dyn FileDownload>) } else { let check_url: String = [url.as_str(), ".sha1"].into_iter().collect(); - Some(Box::new(RemoteChecksumDownload::new(url, check_url, self.home.join(path))) as Box<dyn FileDownload>) + Some(Box::new(RemoteChecksumDownload::new(url, check_url, self.home.join(path), filename)) as Box<dyn FileDownload>) } }; @@ -656,7 +661,7 @@ impl LibraryRepository { // drinking game: take a shot once per heap allocation let path = self.home.join(dlinfo.path.as_ref().map(PathBuf::from).or_else(|| Self::get_artifact_path(lib.name.as_str(), classifier))?); - Some(Box::new(VerifiedDownload::new(dlinfo.url.as_ref()?, path.as_path(), dlinfo.size, dlinfo.sha1))) + Some(Box::new(VerifiedDownload::new(dlinfo.url.as_ref()?, path.as_path(), dlinfo.size, dlinfo.sha1, path_display(&path)))) } else { flat_library("https://libraries.minecraft.net/") } diff --git a/ozone/src/launcher/assets.rs b/ozone/src/launcher/assets.rs index b1f429a..d1df62f 100644 --- a/ozone/src/launcher/assets.rs +++ b/ozone/src/launcher/assets.rs @@ -10,6 +10,7 @@ use sha1_smol::Sha1; use tokio::{fs, io}; use tokio::fs::File; use crate::assets::{Asset, AssetIndex}; +use crate::launcher::ProgressIndication; use super::download::{MultiDownloader, VerifiedDownload}; use crate::util; use crate::util::{FileVerifyError, IntegrityError}; @@ -207,7 +208,7 @@ impl AssetRepository { } } - pub async fn ensure_assets(&self, index: &AssetIndex) -> Result<(), AssetError> { + pub async fn ensure_assets(&self, index: &AssetIndex, progress: &dyn ProgressIndication) -> Result<(), AssetError> { let mut downloads = Vec::new(); let objects_path = [self.home.as_ref(), OsStr::new(OBJECT_PATH)].iter().collect::<PathBuf>(); @@ -216,23 +217,23 @@ impl AssetRepository { error: e })?; - for object in index.objects.values() { + for (key, object) in index.objects.iter() { let path = self.get_object_path(object); Self::ensure_dir(path.parent().unwrap()).await.map_err(|error| AssetError::IO { error, what: "creating directory for object" })?; - downloads.push(VerifiedDownload::new(Self::get_object_url(object), &path, Some(object.size), Some(object.hash))); + downloads.push(VerifiedDownload::new(Self::get_object_url(object), &path, Some(object.size), Some(object.hash), key)); } if self.online { info!("Downloading {} asset objects...", downloads.len()); let client = util::create_client(); - MultiDownloader::with_concurrent(downloads.iter_mut(), 32).perform(&client).await + MultiDownloader::with_concurrent(downloads.iter_mut(), 32).perform(&client, progress).await .inspect_err(|e| warn!("asset download failed: {e}")) .map_err(|_| AssetError::AssetObjectDownload)?; } else { info!("Verifying {} asset objects...", downloads.len()); - super::download::verify_files(downloads.iter()).await.map_err(AssetError::AssetVerifyError)?; + super::download::verify_files(downloads.iter(), progress).await.map_err(AssetError::AssetVerifyError)?; } Ok(()) diff --git a/ozone/src/launcher/download.rs b/ozone/src/launcher/download.rs index cfb246d..1065351 100644 --- a/ozone/src/launcher/download.rs +++ b/ozone/src/launcher/download.rs @@ -15,6 +15,7 @@ use tokio::io::AsyncWriteExt; use crate::launcher::ProgressIndication; use crate::util; use crate::util::IntegrityError; +use crate::util::progress::ItemProgress; #[async_trait] pub trait Download: Debug + Display { @@ -34,7 +35,7 @@ pub struct MultiDownloader<'d, D, DR, I> where D: Download + ?Sized + 'd, DR: BorrowMut<D> + 'd, - I: Iterator<Item = &'d mut DR> + I: ExactSizeIterator<Item = &'d mut DR> { jobs: I, nconcurrent: usize, @@ -103,7 +104,7 @@ impl<'j, D, DR, I> MultiDownloader<'j, D, DR, I> where D: Download + ?Sized + 'j, DR: BorrowMut<D> + 'j, - I: Iterator<Item = &'j mut DR> + I: ExactSizeIterator<Item = &'j mut DR> { pub fn new(jobs: I) -> MultiDownloader<'j, D, DR, I> { Self::with_concurrent(jobs, 24) @@ -129,25 +130,36 @@ where } } - progress.set_all(0, self.jobs.size_hint().0); + progress.set_all(0, self.jobs.len()); + let item_progress = ItemProgress::new(progress); + let item_progress_ref = &item_progress; + item_progress.update_text(); + stream::iter(self.jobs.map(Ok)) .try_for_each_concurrent(self.nconcurrent, |job_| async move { let job = job_.borrow_mut(); - + let job_text = job.to_string(); + let job_idx = item_progress_ref.push(format!("preparing {job_text}")); + let Some(rq) = map_err!(job.prepare(client).await, Phase::Prepare, job) else { + item_progress_ref.finish(job_idx); return Ok(()); }; - + + item_progress_ref.update(job_idx, |i| i.replace_range(.., &format!("requesting {job_text}"))); let mut data = map_err!(map_err!(rq.send().await, Phase::Send, job).error_for_status(), Phase::Send, job).bytes_stream(); + item_progress_ref.update(job_idx, |i| i.replace_range(.., &format!("receiving {job_text}"))); while let Some(bytes) = data.next().await { let bytes = map_err!(bytes, Phase::Receive, job); map_err!(job.handle_chunk(bytes.as_ref()).await, Phase::HandleChunk, job); } + item_progress_ref.update(job_idx, |i| i.replace_range(.., &format!("finishing {job_text}"))); job.finish().await.map_err(|e| PhaseDownloadError::new(Phase::Finish, e, job))?; - progress.inc_progress(1); + + item_progress_ref.finish(job_idx); Ok(()) }).await @@ -158,6 +170,7 @@ pub struct VerifiedDownload { url: String, expect_size: Option<usize>, expect_sha1: Option<Digest>, + display_name: String, path: PathBuf, file: Option<File>, @@ -177,15 +190,16 @@ impl Debug for VerifiedDownload { impl Display for VerifiedDownload { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "downloading {} to {}", self.url, self.path.display()) + f.write_str(&self.display_name) } } impl VerifiedDownload { - pub fn new(url: impl Into<String>, path: impl Into<PathBuf>, expect_size: Option<usize>, expect_sha1: Option<Digest>) -> VerifiedDownload { + pub fn new(url: impl Into<String>, path: impl Into<PathBuf>, expect_size: Option<usize>, expect_sha1: Option<Digest>, display_name: impl Into<String>) -> VerifiedDownload { VerifiedDownload { url: url.into(), path: path.into(), + display_name: display_name.into(), expect_size, expect_sha1, @@ -283,6 +297,7 @@ impl FileDownload for VerifiedDownload { pub struct RemoteChecksumDownload { url: String, check_url: String, + display_name: String, expect_sha1: Option<Digest>, @@ -293,11 +308,12 @@ pub struct RemoteChecksumDownload { } impl RemoteChecksumDownload { - pub fn new(url: impl Into<String>, check_url: impl Into<String>, path: impl Into<PathBuf>) -> RemoteChecksumDownload { + pub fn new(url: impl Into<String>, check_url: impl Into<String>, path: impl Into<PathBuf>, display_name: impl Into<String>) -> RemoteChecksumDownload { RemoteChecksumDownload { url: url.into(), check_url: check_url.into(), path: path.into(), + display_name: display_name.into(), expect_sha1: None, @@ -314,7 +330,7 @@ impl RemoteChecksumDownload { impl Display for RemoteChecksumDownload { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "downloading {} to {} (checksum at {})", self.url, self.path.display(), self.check_url) + f.write_str(&self.display_name) } } @@ -419,14 +435,25 @@ impl FileDownload for RemoteChecksumDownload { } } -pub async fn verify_files<'d, D, DR>(files: impl Iterator<Item = &'d DR>) -> Result<(), Box<dyn Error>> +pub async fn verify_files<'d, D, DR>(files: impl ExactSizeIterator<Item = &'d DR>, progress: &dyn ProgressIndication) -> Result<(), Box<dyn Error>> where D: Download + ?Sized, DR: Borrow<D> + 'd { + let item_progress = ItemProgress::new(progress); + let item_progress_ref = &item_progress; + progress.set_all(0, files.len()); + stream::iter(files.map(Ok)) .try_for_each_concurrent(32, |d| async move { - d.borrow().verify_offline().await + let d = d.borrow(); + let idx = item_progress_ref.push(format!("verifying {d}")); + + d.verify_offline().await?; + + item_progress_ref.finish(idx); + + Ok(()) }) .await } diff --git a/ozone/src/launcher/jre.rs b/ozone/src/launcher/jre.rs index 901a6a0..e94d0c3 100644 --- a/ozone/src/launcher/jre.rs +++ b/ozone/src/launcher/jre.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use std::path::{Component, Path, PathBuf}; @@ -19,7 +20,7 @@ use super::jre::manifest::JavaRuntimeFile; use crate::util; use crate::util::{EnsureFileError, IntegrityError}; use crate::version::DownloadInfo; -use super::constants; +use super::{constants, ProgressIndication}; pub struct JavaRuntimeRepository { online: bool, @@ -179,13 +180,13 @@ impl JavaRuntimeRepository { }).await } - async fn ensure_jre_files(path: &Path, manifest: &JavaRuntimeManifest) -> Result<(), JavaRuntimeError> { + async fn ensure_jre_files(&self, path: &Path, manifest: &JavaRuntimeManifest, progress: &dyn ProgressIndication) -> Result<(), JavaRuntimeError> { let mut downloads = Vec::new(); for (name, file) in manifest.files.iter().filter(|(_, f)| f.is_file()) { let file_path = util::check_path(name).map_err(JavaRuntimeError::MalformedManifest)?; - let file_path = [path, file_path].into_iter().collect::<PathBuf>(); + let file_path_full = [path, file_path].into_iter().collect::<PathBuf>(); - downloads.push(LzmaDownloadJob::try_from((file, file_path)).map_err(|e| { + downloads.push(LzmaDownloadJob::new(file, file_path_full, file_path.to_string_lossy()).map_err(|e| { match e { LzmaDownloadError::MissingURL => JavaRuntimeError::MalformedManifest("runtime manifest missing URL"), LzmaDownloadError::NotAFile => unreachable!("we just made sure this was a file") @@ -193,15 +194,21 @@ impl JavaRuntimeRepository { })?); } - let dl = MultiDownloader::new(downloads.iter_mut()); - let client = util::create_client(); - - dl.perform(&client).await - .inspect_err(|e| warn!("jre file download failed: {e}")) - .map_err(|_| JavaRuntimeError::MultiDownloadError) + if self.online { + let dl = MultiDownloader::new(downloads.iter_mut()); + let client = util::create_client(); + + dl.perform(&client, progress).await + .inspect_err(|e| warn!("jre file download failed: {e}")) + .map_err(|_| JavaRuntimeError::MultiDownloadError) + } else { + super::download::verify_files(downloads.iter(), progress).await + .inspect_err(|e| warn!("jre file verify failed: {e}")) + .map_err(|e| JavaRuntimeError::MultiDownloadError) + } } - async fn ensure_links(root_path: &Path, manifest: &JavaRuntimeManifest) -> Result<(), JavaRuntimeError> { + async fn ensure_links(&self, root_path: &Path, manifest: &JavaRuntimeManifest) -> Result<(), JavaRuntimeError> { stream::iter(manifest.files.iter().filter(|(_, f)| f.is_link())) .map::<Result<_, JavaRuntimeError>, _>(|(name, file)| Ok(async move { let JavaRuntimeFile::Link { target } = file else { @@ -259,7 +266,7 @@ impl JavaRuntimeRepository { .try_fold((), |_, _| async { Ok(()) }).await } - pub async fn ensure_jre(&self, component: &str, manifest: JavaRuntimeManifest) -> Result<PathBuf, JavaRuntimeError> { + pub async fn ensure_jre(&self, component: &str, manifest: JavaRuntimeManifest, progress: &dyn ProgressIndication) -> Result<PathBuf, JavaRuntimeError> { let runtime_path = self.get_component_dir(component); let runtime_path = runtime_path.join("runtime"); let manifest = Arc::new(manifest); @@ -267,18 +274,24 @@ impl JavaRuntimeRepository { fs::create_dir_all(&runtime_path).await .map_err(|e| JavaRuntimeError::IO { what: "creating runtime directory", error: e })?; + progress.set_all(0, 1); + debug!("Cleaning up JRE directory for {component}"); + progress.set_text(Cow::Borrowed("Cleaning up runtime directory")); Self::clean_up_runtime(runtime_path.as_path(), manifest.clone()).await .map_err(|e| JavaRuntimeError::IO { what: "cleaning up runtime directory", error: e })?; debug!("Building directory structure for {component}"); + progress.set_text(Cow::Borrowed("Building directory structure")); self.ensure_jre_dirs(&runtime_path, manifest.as_ref()).await?; debug!("Downloading JRE files for {component}"); - Self::ensure_jre_files(&runtime_path, manifest.as_ref()).await?; + progress.set_text(Cow::Borrowed("Downloading JRE files")); + self.ensure_jre_files(&runtime_path, manifest.as_ref(), progress).await?; debug!("Ensuring symbolic links for {component}"); - Self::ensure_links(&runtime_path, manifest.as_ref()).await?; + progress.set_text(Cow::Borrowed("Creating symbolic links")); + self.ensure_links(&runtime_path, manifest.as_ref()).await?; Ok(runtime_path) } diff --git a/ozone/src/launcher/jre/download.rs b/ozone/src/launcher/jre/download.rs index 6df1413..241ffdc 100644 --- a/ozone/src/launcher/jre/download.rs +++ b/ozone/src/launcher/jre/download.rs @@ -25,8 +25,9 @@ pub struct LzmaDownloadJob { url: String, path: PathBuf, inflate: bool, + display_name: String, - #[allow(dead_code)] // not used on windows + #[cfg_attr(windows, allow(dead_code))] // not used on windows executable: bool, raw_size: Option<usize>, @@ -40,12 +41,13 @@ pub struct LzmaDownloadJob { } impl LzmaDownloadJob { - fn new_inflate(raw: &DownloadInfo, lzma: &DownloadInfo, exe: bool, path: PathBuf) -> Result<Self, LzmaDownloadError> { + fn new_inflate(raw: &DownloadInfo, lzma: &DownloadInfo, exe: bool, path: PathBuf, display_name: impl Into<String>) -> Result<Self, LzmaDownloadError> { Ok(LzmaDownloadJob { url: lzma.url.as_ref().map_or_else(|| Err(LzmaDownloadError::MissingURL), |u| Ok(u.to_owned()))?, path, inflate: true, executable: exe, + display_name: display_name.into(), raw_size: raw.size, raw_sha1: raw.sha1, @@ -58,12 +60,13 @@ impl LzmaDownloadJob { }) } - fn new_raw(raw: &DownloadInfo, exe: bool, path: PathBuf) -> Result<Self, LzmaDownloadError> { + fn new_raw(raw: &DownloadInfo, exe: bool, path: PathBuf, display_name: impl Into<String>) -> Result<Self, LzmaDownloadError> { Ok(LzmaDownloadJob { url: raw.url.as_ref().map_or_else(|| Err(LzmaDownloadError::MissingURL), |u| Ok(u.to_owned()))?, path, inflate: false, executable: exe, + display_name: display_name.into(), raw_size: raw.size, raw_sha1: raw.sha1, @@ -77,10 +80,8 @@ impl LzmaDownloadJob { } } -impl TryFrom<(&JavaRuntimeFile, PathBuf)> for LzmaDownloadJob { - type Error = LzmaDownloadError; - - fn try_from((file, path): (&JavaRuntimeFile, PathBuf)) -> Result<Self, Self::Error> { +impl LzmaDownloadJob { + pub(super) fn new(file: &JavaRuntimeFile, path: PathBuf, display: impl Into<String>) -> Result<Self, LzmaDownloadError> { if !file.is_file() { return Err(LzmaDownloadError::NotAFile); } @@ -90,8 +91,8 @@ impl TryFrom<(&JavaRuntimeFile, PathBuf)> for LzmaDownloadJob { }; match downloads.lzma.as_ref() { - Some(lzma) => LzmaDownloadJob::new_inflate(&downloads.raw, lzma, *executable, path), - None => LzmaDownloadJob::new_raw(&downloads.raw, *executable, path) + Some(lzma) => LzmaDownloadJob::new_inflate(&downloads.raw, lzma, *executable, path, display), + None => LzmaDownloadJob::new_raw(&downloads.raw, *executable, path, display) } } } @@ -109,9 +110,9 @@ impl Debug for LzmaDownloadJob { impl Display for LzmaDownloadJob { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if self.inflate { - write!(f, "download and inflate {} to {}", &self.url, self.path.display()) + write!(f, "{} (compressed)", self.display_name) } else { - write!(f, "download {} to {}", &self.url, self.path.display()) + f.write_str(&self.display_name) } } } diff --git a/ozone/src/util/progress.rs b/ozone/src/util/progress.rs index ccb610e..4d2b5e3 100644 --- a/ozone/src/util/progress.rs +++ b/ozone/src/util/progress.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use std::collections::BTreeSet; -use std::ops::{Deref, DerefMut}; +use std::collections::BTreeMap; +use std::fmt::Display; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; @@ -61,63 +61,73 @@ impl Step<'_, '_> { } } -struct ProgressItem<T> { - inner: T, - idx: usize +struct ItemProgressInner<'p, T> { + inner: &'p dyn ProgressIndication, + items: BTreeMap<usize, T>, + cur: usize, } -impl<T> Deref for ProgressItem<T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.inner - } +pub(crate) struct ItemProgress<'p, T> { + inner: Mutex<ItemProgressInner<'p, T>>, } -impl<T> DerefMut for ProgressItem<T> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner +impl<'p, T: Display> ItemProgressInner<'p, T> { + fn update_text(&self) { + match self.items.first_key_value() { + Some((_, item)) => { + match self.items.len() { + 1 => self.inner.set_text(Cow::Owned(item.to_string())), + 0 => unreachable!(), + l => self.inner.set_text(Cow::Owned(format!("{item} (+{} more)", l - 1))) + } + }, + None => self.inner.set_text(Cow::Borrowed("...")) + } } } -struct ProgressItemTracker<T> { - set: BTreeSet<ProgressItem<T>>, - cur: usize -} - -impl<T> PartialEq for ProgressItem<T> { - fn eq(&self, other: &Self) -> bool { - self.idx.eq(&other.idx) +impl<'p, T: Display> ItemProgress<'p, T> { + pub(crate) fn new(inner: &'p dyn ProgressIndication) -> Self { + Self { + inner: Mutex::new(ItemProgressInner { + inner, + items: BTreeMap::new(), + cur: 0 + }) + } } -} - -impl<T> PartialOrd for ProgressItem<T> { - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - Some(self.cmp(other)) + + pub(crate) fn update_text(&self) { + let inner = self.inner.lock().unwrap(); + inner.update_text(); } -} + + pub(crate) fn push(&self, item: T) -> usize { + let mut inner = self.inner.lock().unwrap(); -impl<T> Eq for ProgressItem<T> {} + let idx = inner.cur; + inner.cur += 1; + inner.items.insert(idx, item); + inner.update_text(); -impl<T> Ord for ProgressItem<T> { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.idx.cmp(&other.idx) + idx } -} -impl<T> ProgressItemTracker<T> { - fn push(&mut self, item: T) { - let idx = self.cur; - self.cur += 1; - - self.set.insert(ProgressItem { inner: item, idx }); - } - - fn len(&self) -> usize { - self.set.len() + pub(crate) fn finish(&self, idx: usize) { + let mut inner = self.inner.lock().unwrap(); + + if inner.items.remove(&idx).is_some() { + inner.inner.inc_progress(1); + inner.update_text(); + } } - - fn first(&self) -> Option<&T> { - self.set.first().map(|t| &**t) + + pub(crate) fn update<F>(&self, idx: usize, update_fn: F) + where + F: FnOnce(&mut T) + { + let mut inner = self.inner.lock().unwrap(); + if let Some(item) = inner.items.get_mut(&idx) { update_fn(item) } + inner.update_text(); } } |
