summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ozone-cli/src/main.rs2
-rw-r--r--src/launcher.rs70
-rw-r--r--src/launcher/extract.rs105
3 files changed, 145 insertions, 32 deletions
diff --git a/ozone-cli/src/main.rs b/ozone-cli/src/main.rs
index e4d1528..dd59f9d 100644
--- a/ozone-cli/src/main.rs
+++ b/ozone-cli/src/main.rs
@@ -15,7 +15,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
let launcher = o3launcher::launcher::Launcher::new(PathBuf::from("./work").as_path(), true).await?;
let profile = Profile {
- version_id: "25w02a".into(),
+ version_id: "1.8.9".into(),
java_runtime: None,
instance: "".into()
};
diff --git a/src/launcher.rs b/src/launcher.rs
index f712e69..eac79bb 100644
--- a/src/launcher.rs
+++ b/src/launcher.rs
@@ -5,6 +5,7 @@ mod strsub;
mod download;
mod rules;
mod assets;
+mod extract;
use std::borrow::Cow;
use std::cmp::min;
@@ -162,7 +163,7 @@ pub enum LaunchError {
LibraryDirError(PathBuf, io::Error),
LibraryVerifyError(FileVerifyError),
LibraryDownloadError,
- LibraryExtractZipError(PathBuf, zip::result::ZipError),
+ LibraryExtractError(extract::ZipExtractError),
// ensure file errors
MissingURL,
@@ -189,7 +190,7 @@ impl Display for LaunchError {
LaunchError::LibraryDirError(path, e) => write!(f, "failed to create library directory {}: {}", path.display(), e),
LaunchError::LibraryVerifyError(e) => write!(f, "failed to verify library: {}", e),
LaunchError::LibraryDownloadError => f.write_str("library download failed (see above logs for details)"), // TODO: booo this sucks
- LaunchError::LibraryExtractZipError(path, e) => write!(f, "library extract zip error ({}): {e}", path.display()),
+ LaunchError::LibraryExtractError(e) => write!(f, "library extract zip error: {e}"),
LaunchError::MissingURL => f.write_str("cannot download required file, URL is missing"),
LaunchError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error),
LaunchError::Offline => f.write_str("cannot download file in offline mode"),
@@ -211,7 +212,7 @@ impl Error for LaunchError {
LaunchError::IncompatibleVersion(e) => Some(e),
LaunchError::LibraryDirError(_, e) => Some(e),
LaunchError::LibraryVerifyError(e) => Some(e),
- LaunchError::LibraryExtractZipError(_, e) => Some(e),
+ LaunchError::LibraryExtractError(e) => Some(e),
LaunchError::IO { error: e, .. } => Some(e),
LaunchError::Download { error: e, .. } => Some(e),
LaunchError::Integrity(e) => Some(e),
@@ -267,10 +268,15 @@ impl Launcher {
lib.natives.as_ref().map_or(None, |n| n.get(&self.system_info.os)).map(|s| s.as_str())
}
- fn create_extract_job<'lib>(&self, lib: &'lib Library) -> Option<LibraryExtractJob<'lib>> {
+ fn create_extract_job(&self, lib: &Library) -> Option<LibraryExtractJob> {
+ if lib.natives.is_none() {
+ debug!("Not extracting natives for {}, no \"natives\".", lib.name);
+ return None; // do not extract this one
+ }
+
Some(LibraryExtractJob {
- source: LibraryRepository::get_artifact_path(lib.name.as_str(), self.choose_lib_classifier(lib))?,
- rule: lib.extract.as_ref()
+ source: self.libraries.get_full_artifact_path(lib.name.as_str(), self.choose_lib_classifier(lib))?,
+ rule: lib.extract.clone()
})
}
@@ -504,7 +510,11 @@ impl Launcher {
let nnatives = self.libraries.clean_old_natives().await?;
info!("Cleaned up {} old natives directories.", nnatives);
- // extract natives
+ // extract natives (execute this function unconditionally because we still need the natives dir to exist)
+ info!("Extracting natives.");
+ let natives_dir = self.libraries.extract_natives(libs.iter().filter_map(|lib| {
+ self.create_extract_job(*lib)
+ }).collect::<Vec<_>>()).await?;
//todo!()
Ok(())
@@ -535,9 +545,9 @@ impl Error for LibraryError {
}
}
-struct LibraryExtractJob<'lib> {
+struct LibraryExtractJob {
source: PathBuf,
- rule: Option<&'lib LibraryExtractRule>
+ rule: Option<LibraryExtractRule>
}
const ARCH_BITS: &'static str = formatcp!("{}", usize::BITS);
@@ -582,6 +592,10 @@ impl LibraryRepository {
p.push(Self::get_artifact_filename(name, classifier)?);
Some(p)
}
+
+ fn get_full_artifact_path(&self, name: &str, classifier: Option<&str>) -> Option<PathBuf> {
+ Some(self.home.join(Self::get_artifact_path(name, classifier)?))
+ }
fn create_download(&self, lib: &Library, classifier: Option<&str>) -> Option<VerifiedDownload> {
if lib.url.is_some() || lib.downloads.is_none() {
@@ -645,18 +659,7 @@ impl LibraryRepository {
}).await
}
- fn extract_natives_for<'lib>(natives: &Path, job: LibraryExtractJob<'lib>) -> Result<usize, LaunchError> {
- /*let file = File::open(&job.source).await.map_err(|e| LaunchError::IO {
- what: "extracting library natives (open)",
- error: e
- })?;
-
- let z = ZipArchive::new(file);*/
-
- todo!()
- }
-
- async fn extract_natives<'lib>(&self, libs: impl IntoIterator<Item = LibraryExtractJob<'lib>>) -> Result<PathBuf, LaunchError> {
+ async fn extract_natives<'lib>(&self, libs: Vec<LibraryExtractJob>) -> Result<PathBuf, LaunchError> {
fs::create_dir_all(&self.natives).await.map_err(|e| LaunchError::IO {
what: "creating natives directory",
error: e
@@ -671,17 +674,22 @@ impl LibraryRepository {
error: e
})?;
- let natives_dir_ref = natives_dir.as_path();
+ let (path_again, extracted) = tokio::task::spawn_blocking(move || {
+ let mut tally = 0usize;
- stream::iter(libs)
- .map(|lib| Ok(async move {
- // TODO: Self::extract_natives_for(natives_dir_ref, lib)
- todo!() as Result<usize, _>
- }))
- .try_buffer_unordered(8)
- .try_fold(0usize, |accum, num| async move {Ok(accum + num)})
- .await.inspect(|num| debug!("Extracted {} native entries to {}", num, natives_dir_ref.display()))
- .map(|_| natives_dir)
+ for job in libs {
+ tally += extract::extract_zip(&job.source, &natives_dir, |name|
+ job.rule.as_ref().is_some_and(|rules|
+ rules.exclude.iter().filter(|ex|
+ name.starts_with(ex.as_str())).next().is_some()))?;
+ }
+
+ Ok((natives_dir, tally))
+ }).await.unwrap().map_err(LaunchError::LibraryExtractError)?;
+
+ info!("Done extracting natives! Copied {} files.", extracted);
+
+ Ok(path_again)
}
}
diff --git a/src/launcher/extract.rs b/src/launcher/extract.rs
new file mode 100644
index 0000000..c9e0dc9
--- /dev/null
+++ b/src/launcher/extract.rs
@@ -0,0 +1,105 @@
+use std::error::Error;
+use std::fmt::{Display, Formatter};
+use std::{fs, io};
+use std::fs::File;
+use std::io::{BufReader, Error as IOError};
+use std::path::{Component, Path, PathBuf};
+use log::debug;
+use zip::result::ZipError;
+use zip::ZipArchive;
+
+#[derive(Debug)]
+pub enum ZipExtractError {
+ IO { what: &'static str, error: IOError },
+ Zip { what: &'static str, error: ZipError },
+ InvalidEntry { why: &'static str, name: String }
+}
+
+impl From<(&'static str, IOError)> for ZipExtractError {
+ fn from((what, error): (&'static str, IOError)) -> Self {
+ ZipExtractError::IO { what, error }
+ }
+}
+
+impl From<(&'static str, ZipError)> for ZipExtractError {
+ fn from((what, error): (&'static str, ZipError)) -> Self {
+ ZipExtractError::Zip { what, error }
+ }
+}
+
+impl Display for ZipExtractError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ZipExtractError::IO { what, error } => write!(f, "i/o error ({what}): {error}"),
+ ZipExtractError::Zip { what, error } => write!(f, "zip error ({what}): {error}"),
+ ZipExtractError::InvalidEntry { why, name } => write!(f, "invalid entry in zip file ({why}): {name}")
+ }
+ }
+}
+
+impl Error for ZipExtractError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ ZipExtractError::IO { error, .. } => Some(error),
+ ZipExtractError::Zip { error, .. } => Some(error),
+ _ => None
+ }
+ }
+}
+
+pub fn extract_zip<F>(zip_path: impl AsRef<Path>, extract_root: impl AsRef<Path>, condition: F) -> Result<usize, ZipExtractError>
+where
+ F: Fn(&str) -> bool
+{
+ debug!("Extracting zip file {} into {}", zip_path.as_ref().display(), extract_root.as_ref().display());
+
+ fs::create_dir_all(&extract_root).map_err(|e| ZipExtractError::from(("create extract root", e)))?;
+
+ let mut extracted = 0usize;
+
+ let file = File::open(zip_path).map_err(|e| ZipExtractError::from(("extract zip file (open)", e)))?;
+ let read = BufReader::new(file);
+
+ let mut archive = ZipArchive::new(read).map_err(|e| ZipExtractError::from(("read zip archive", e)))?;
+
+ for n in 0..archive.len() {
+ let mut entry = archive.by_index(n).map_err(|e| ZipExtractError::from(("access zip entry", e)))?;
+ let name = entry.name();
+
+ if !condition(name) {
+ continue;
+ }
+
+ let entry_path: &Path = Path::new(name);
+ let mut depth = 0usize;
+ for component in entry_path.components() {
+ depth = match component {
+ Component::Prefix(_) | Component::RootDir =>
+ return Err(ZipExtractError::InvalidEntry {
+ why: "root path component in entry",
+ name: name.to_owned()
+ }),
+ Component::ParentDir => depth.checked_sub(1)
+ .map_or_else(|| Err(ZipExtractError::InvalidEntry {
+ why: "entry path escapes extraction root",
+ name: name.to_owned()
+ }), |s| Ok(s))?,
+ Component::Normal(_) => depth + 1,
+ _ => depth
+ }
+ }
+
+ let entry_path: PathBuf = [extract_root.as_ref(), entry_path].iter().collect();
+
+ // hmm some redundant directory creations will be happening here on linux :(
+ if let Some(parent) = entry_path.parent() {
+ fs::create_dir_all(parent).map_err(|e| ZipExtractError::from(("create entry directory", e)))?;
+ }
+
+ let mut outfile = File::create(&entry_path).map_err(|e| ZipExtractError::from(("extract zip entry (open)", e)))?;
+ io::copy(&mut entry, &mut outfile).map_err(|e| ZipExtractError::from(("extract zip entry (write)", e)))?;
+ extracted += 1;
+ }
+
+ Ok(extracted)
+}