summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-03-15 19:59:11 -0500
committerLibravatar bigfoot547 <[email protected]>2025-03-15 19:59:11 -0500
commit7bd8a5624e0f6340af8bb930ee98b3386114705b (patch)
tree2c7c7948a43839f4f00f557b4836db2176a352da
parentwip: progress (diff)
granular progress indication
the code for the cli kinda sucks
-rw-r--r--Cargo.lock11
-rw-r--r--ozone-cli/Cargo.toml1
-rw-r--r--ozone-cli/src/main.rs16
-rw-r--r--ozone/src/launcher.rs19
-rw-r--r--ozone/src/launcher/assets.rs11
-rw-r--r--ozone/src/launcher/download.rs51
-rw-r--r--ozone/src/launcher/jre.rs41
-rw-r--r--ozone/src/launcher/jre/download.rs23
-rw-r--r--ozone/src/util/progress.rs102
9 files changed, 175 insertions, 100 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a65b444..8dbc5ec 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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();
}
}