summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock45
-rw-r--r--ozone-cli/Cargo.toml1
-rw-r--r--ozone-cli/src/main.rs80
-rw-r--r--ozone/src/launcher.rs46
-rw-r--r--ozone/src/launcher/download.rs5
-rw-r--r--ozone/src/launcher/jre/download.rs2
-rw-r--r--ozone/src/util.rs2
-rw-r--r--ozone/src/util/progress.rs123
8 files changed, 292 insertions, 12 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8bc5e5e..a65b444 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -930,6 +930,19 @@ dependencies = [
]
[[package]]
+name = "console"
+version = "0.15.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "once_cell",
+ "unicode-width",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
name = "const-field-offset"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1395,6 +1408,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
+[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2590,6 +2609,19 @@ dependencies = [
]
[[package]]
+name = "indicatif"
+version = "0.17.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
+dependencies = [
+ "console",
+ "number_prefix",
+ "portable-atomic",
+ "unicode-width",
+ "web-time",
+]
+
+[[package]]
name = "input"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3100,6 +3132,12 @@ dependencies = [
]
[[package]]
+name = "number_prefix"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
+
+[[package]]
name = "oauth2"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3538,6 +3576,7 @@ version = "0.1.0"
dependencies = [
"clap",
"clap_complete",
+ "indicatif",
"lazy_static",
"log",
"ozone",
@@ -5208,6 +5247,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
+[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/ozone-cli/Cargo.toml b/ozone-cli/Cargo.toml
index db6503b..ad65d92 100644
--- a/ozone-cli/Cargo.toml
+++ b/ozone-cli/Cargo.toml
@@ -13,3 +13,4 @@ clap_complete = { version = "4.5.44", features = ["unstable-dynamic"] }
lazy_static = "1.5.0"
regex = "1.11.1"
uuid = { version = "1.12.1", features = ["v4"] }
+indicatif = "0.17.11"
diff --git a/ozone-cli/src/main.rs b/ozone-cli/src/main.rs
index 11349c8..dda2c32 100644
--- a/ozone-cli/src/main.rs
+++ b/ozone-cli/src/main.rs
@@ -1,11 +1,14 @@
mod cli;
+use std::borrow::Cow;
use std::error::Error;
use std::path::Path;
use std::process::ExitCode;
+use std::time::Duration;
use log::{error, info, trace};
use clap::Parser;
-use ozone::launcher::{Instance, JavaRuntimeSetting, Launcher, Settings, ALT_CLIENT_ID, MAIN_CLIENT_ID};
+use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
+use ozone::launcher::{Instance, JavaRuntimeSetting, LaunchProgress, Launcher, ProgressIndication, Settings, ALT_CLIENT_ID, MAIN_CLIENT_ID};
use ozone::launcher::version::{VersionList, VersionResult};
use uuid::Uuid;
use ozone::auth::{Account, AccountStorage, MsaAccount};
@@ -14,6 +17,51 @@ use crate::cli::{AccountCommand, Cli, InstanceCommand, ProfileSelectArgs, RootCo
const ACCOUNT_DB_PATH: &str = "ozone_accounts.json";
+struct IndicatifProgress {
+ bar: ProgressBar
+}
+
+impl ProgressIndication for IndicatifProgress {
+ fn set_progress(&self, progress: usize) {
+ self.bar.set_position(progress as u64);
+ }
+
+ fn set_max(&self, max: usize) {
+ self.bar.set_length(max as u64);
+ }
+
+ fn set_all(&self, progress: usize, max: usize) {
+ self.bar.update(|s| {
+ s.set_pos(progress as u64);
+ s.set_len(max as u64);
+ });
+ }
+
+ fn inc_progress(&self, by: usize) {
+ self.bar.inc(by as u64);
+ }
+
+ fn inc_max(&self, by: usize) {
+ self.bar.inc_length(by as u64);
+ }
+
+ fn dec_progress(&self, by: usize) {
+ self.bar.dec(by as u64);
+ }
+
+ fn dec_max(&self, by: usize) {
+ self.bar.dec_length(by as u64);
+ }
+
+ fn set_text(&self, text: Cow<'static, str>) {
+ self.bar.set_message(text);
+ }
+
+ fn complete(&self, message: Cow<'static, str>) {
+ self.bar.finish_with_message(message);
+ }
+}
+
fn find_account<'a>(auth: &'a AccountStorage, input: &ProfileSelectArgs) -> Result<Vec<(&'a String, &'a Account)>, ()> {
if let Some(uuid) = input.uuid {
Ok(auth.iter_accounts().filter(|(_, a)| match *a {
@@ -467,12 +515,30 @@ 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 launch = launcher.prepare_launch(inst, Settings::get_instance_path(selection), settings.client_id, account, args.demo).await.map_err(|e| {
- error!("error launching: {e}");
- e
- })?;
+ 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())
+ };
+
+ progress.bar.enable_steady_tick(Duration::from_millis(100));
+
+ let lp = LaunchProgress::new(&progress as &dyn ProgressIndication, &progress_sub as &dyn ProgressIndication);
+
+ let launch = launcher.prepare_launch(inst,
+ Settings::get_instance_path(selection),
+ settings.client_id,
+ account,
+ args.demo,
+ lp)
+ .await.inspect_err(|e| {
+ error!("error launching: {e}");
+ })?;
- dbg!(&launch);
info!("ok!");
println!("Launching the game!");
@@ -487,7 +553,7 @@ 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();
+ //simple_logger::SimpleLogger::new().env().init().unwrap();
let arg = Cli::parse();
diff --git a/ozone/src/launcher.rs b/ozone/src/launcher.rs
index 98e8143..b21f873 100644
--- a/ozone/src/launcher.rs
+++ b/ozone/src/launcher.rs
@@ -36,6 +36,7 @@ use crate::version::{Library, OSRestriction, OperatingSystem, DownloadType, Libr
use assets::{AssetError, AssetRepository};
use ozone_helpers::ozone_arch_bits;
use crate::util::{self, AsJavaPath};
+pub use crate::util::progress::ProgressIndication;
pub use constants::{MAIN_CLIENT_ID, ALT_CLIENT_ID};
pub use settings::*;
@@ -47,6 +48,7 @@ use crate::auth::Account;
use crate::launcher::download::{FileDownload, RemoteChecksumDownload};
use crate::launcher::jre::{JavaRuntimeError, JavaRuntimeRepository};
use crate::launcher::version::VersionError;
+use crate::util::progress::StepProgress;
use crate::version::manifest::VersionType;
#[derive(Debug)]
@@ -244,6 +246,17 @@ impl FeatureMatcher<'_> for LaunchFeatureMatcher<'_> {
}
}
+pub struct LaunchProgress<'ind> {
+ indicator: &'ind dyn ProgressIndication,
+ indicator_sub: &'ind dyn ProgressIndication
+}
+
+impl<'ind> LaunchProgress<'ind> {
+ pub fn new(ind: impl Into<&'ind dyn ProgressIndication>, ind_sub: impl Into<&'ind dyn ProgressIndication>) -> Self {
+ Self { indicator: ind.into(), indicator_sub: ind_sub.into() }
+ }
+}
+
impl Launcher {
pub fn sensible_home() -> Option<PathBuf> {
env::var_os("OZONE_HOME")
@@ -326,7 +339,21 @@ impl Launcher {
/* TODO:
* - launch game using JNI
*/
- pub async fn prepare_launch(&self, instance: &Instance, inst_home: impl AsRef<Path>, client_id: Uuid, account: &Account, force_demo: bool) -> Result<Launch, LaunchError> {
+ 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> {
+ let steps: StepProgress = progress.indicator.into();
+ let step_init = steps.step("Resolve game version");
+ let step_prep_libraries = steps.step("Collect required libraries");
+ let step_get_libraries = steps.step("Ensure libraries");
+ let step_log = steps.step("Set up logging");
+ let step_assets = steps.step("Ensure game assets");
+ let step_client_jar = steps.step("Ensure client jar");
+ let step_natives = steps.step("Extract natives");
+ let step_classpath = steps.step("Build classpath");
+ let step_java_runtime = steps.step("Ensure java runtime");
+ let step_arguments = steps.step("Build argument list");
+ steps.update_max();
+
+ step_init.make_current();
let start = Instant::now();
let feature_matcher = LaunchFeatureMatcher { instance, account, force_demo };
let version_id = &instance.game_version;
@@ -358,6 +385,8 @@ impl Launcher {
info!("Resolved launch version {}!", ver.id);
+ step_prep_libraries.make_current();
+
let mut extract_jobs = Vec::new();
let mut downloads = IndexMap::new();
@@ -393,10 +422,12 @@ impl Launcher {
}
}
+ step_get_libraries.make_current();
+
if self.online {
info!("Downloading {} libraries...", downloads.len());
let client = util::create_client();
- MultiDownloader::<dyn FileDownload, _, _>::new(downloads.values_mut()).perform(&client).await
+ MultiDownloader::<dyn FileDownload, _, _>::new(downloads.values_mut()).perform(&client, progress.indicator_sub).await
.inspect_err(|e| warn!("library download failed: {e}"))
.map_err(|_| LaunchError::LibraryDownloadError)?;
} else {
@@ -408,6 +439,8 @@ impl Launcher {
})?;
}
+ step_log.make_current();
+
let log_arg;
if let Some(logging) = ver.logging.as_ref().and_then(|l| l.client.as_ref()) {
log_arg = Some(self.log_config_ensure(logging).await?);
@@ -416,6 +449,7 @@ impl Launcher {
}
// download assets
+ step_assets.make_current();
let (asset_idx_name, asset_idx) =
if let Some(idx_download) = ver.asset_index.as_ref() {
@@ -431,6 +465,7 @@ impl Launcher {
};
// download client jar
+ step_client_jar.make_current();
let client_jar_path;
if let Some(client) = ver.downloads.get(&DownloadType::Client) {
@@ -449,6 +484,8 @@ impl Launcher {
client_jar_path = None;
}
+ step_natives.make_current();
+
// clean up old natives
let nnatives = self.libraries.clean_old_natives().await?;
info!("Cleaned up {} old natives directories.", nnatives);
@@ -466,6 +503,7 @@ impl Launcher {
};
info!("Building classpath");
+ step_classpath.make_current();
let classpath = env::join_paths(downloads.values()
.map(|job| job.get_path().as_java_path())
.chain(client_jar_path.iter().map(|p| p.as_path().as_java_path())))
@@ -479,6 +517,7 @@ impl Launcher {
trace!("Classpath: {classpath}");
info!("Resolving java runtime environment path");
+ step_java_runtime.make_current();
let runtime_path;
if let Some(JavaRuntimeSetting::Path(ref jre_path)) = instance.java_runtime {
@@ -501,10 +540,10 @@ impl Launcher {
return Err(LaunchError::MissingJavaRuntime);
};
-
debug!("Found runtime exe: {}", runtime_exe_path.display());
info!("Deriving launch arguments");
+ step_arguments.make_current();
let info = LaunchInfo {
launcher: self,
client_id,
@@ -539,6 +578,7 @@ impl Launcher {
let diff = Instant::now().duration_since(start);
info!("Finished preparing launch for {} in {:.02} seconds!", ver.id, diff.as_secs_f32());
+ progress.indicator.complete(Cow::Borrowed("Launch preparation complete!"));
Ok(Launch {
jvm_args,
diff --git a/ozone/src/launcher/download.rs b/ozone/src/launcher/download.rs
index 6e32e26..cfb246d 100644
--- a/ozone/src/launcher/download.rs
+++ b/ozone/src/launcher/download.rs
@@ -12,6 +12,7 @@ use reqwest::{Client, RequestBuilder, Response, StatusCode};
use sha1_smol::{Digest, Sha1};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
+use crate::launcher::ProgressIndication;
use crate::util;
use crate::util::IntegrityError;
@@ -118,7 +119,7 @@ where
}
}
- pub async fn perform(self, client: &'j Client) -> Result<(), PhaseDownloadError> {
+ pub async fn perform(self, client: &'j Client, progress: &dyn ProgressIndication) -> Result<(), PhaseDownloadError> {
macro_rules! map_err {
($result:expr, $phase:expr, $job:expr) => {
match $result {
@@ -128,6 +129,7 @@ where
}
}
+ progress.set_all(0, self.jobs.size_hint().0);
stream::iter(self.jobs.map(Ok))
.try_for_each_concurrent(self.nconcurrent, |job_| async move {
let job = job_.borrow_mut();
@@ -145,6 +147,7 @@ where
}
job.finish().await.map_err(|e| PhaseDownloadError::new(Phase::Finish, e, job))?;
+ progress.inc_progress(1);
Ok(())
}).await
diff --git a/ozone/src/launcher/jre/download.rs b/ozone/src/launcher/jre/download.rs
index fc457ef..6df1413 100644
--- a/ozone/src/launcher/jre/download.rs
+++ b/ozone/src/launcher/jre/download.rs
@@ -25,6 +25,8 @@ pub struct LzmaDownloadJob {
url: String,
path: PathBuf,
inflate: bool,
+
+ #[allow(dead_code)] // not used on windows
executable: bool,
raw_size: Option<usize>,
diff --git a/ozone/src/util.rs b/ozone/src/util.rs
index b765a17..6020be0 100644
--- a/ozone/src/util.rs
+++ b/ozone/src/util.rs
@@ -1,4 +1,4 @@
-mod progress;
+pub(crate) mod progress;
use std::error::Error;
use std::fmt::{Display, Formatter};
diff --git a/ozone/src/util/progress.rs b/ozone/src/util/progress.rs
index e69de29..ccb610e 100644
--- a/ozone/src/util/progress.rs
+++ b/ozone/src/util/progress.rs
@@ -0,0 +1,123 @@
+use std::borrow::Cow;
+use std::collections::BTreeSet;
+use std::ops::{Deref, DerefMut};
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Mutex;
+
+pub trait ProgressIndication {
+ fn set_progress(&self, progress: usize);
+ fn set_max(&self, max: usize);
+ fn set_all(&self, progress: usize, max: usize);
+
+ fn inc_progress(&self, by: usize);
+ fn inc_max(&self, by: usize);
+
+ fn dec_progress(&self, by: usize);
+ fn dec_max(&self, by: usize);
+
+ fn set_text(&self, text: Cow<'static, str>);
+
+ fn complete(&self, message: Cow<'static, str>);
+}
+
+pub(crate) struct Step<'ind, 'parent> {
+ parent: &'parent StepProgress<'ind>,
+ text: Cow<'static, str>,
+ index: usize
+}
+
+pub(crate) struct StepProgress<'ind> {
+ inner: &'ind dyn ProgressIndication,
+ curidx: AtomicUsize
+}
+
+impl<'ind> Into<StepProgress<'ind>> for &'ind dyn ProgressIndication {
+ fn into(self) -> StepProgress<'ind> {
+ StepProgress {
+ inner: self,
+ curidx: AtomicUsize::new(0)
+ }
+ }
+}
+
+impl<'ind> StepProgress<'ind> {
+ pub(crate) fn step(&self, name: impl Into<Cow<'static, str>>) -> Step<'ind, '_> {
+ Step {
+ parent: self,
+ text: name.into(),
+ index: self.curidx.fetch_add(1, Ordering::SeqCst)
+ }
+ }
+
+ pub(crate) fn update_max(&self) {
+ self.inner.set_max(self.curidx.load(Ordering::SeqCst));
+ }
+}
+
+impl Step<'_, '_> {
+ pub(crate) fn make_current(&self) {
+ self.parent.inner.set_progress(self.index);
+ self.parent.inner.set_text(self.text.clone());
+ }
+}
+
+struct ProgressItem<T> {
+ inner: T,
+ idx: usize
+}
+
+impl<T> Deref for ProgressItem<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+impl<T> DerefMut for ProgressItem<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.inner
+ }
+}
+
+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<T> PartialOrd for ProgressItem<T> {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl<T> Eq for ProgressItem<T> {}
+
+impl<T> Ord for ProgressItem<T> {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.idx.cmp(&other.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()
+ }
+
+ fn first(&self) -> Option<&T> {
+ self.set.first().map(|t| &**t)
+ }
+}