diff options
| author | 2025-01-16 23:39:40 -0600 | |
|---|---|---|
| committer | 2025-01-16 23:39:40 -0600 | |
| commit | 5c74cae3ca4f97b49fe8858c6fa84d224fc02ccf (patch) | |
| tree | 7e525dbc98a48c4adcf89f56ae492e3ee64881d2 /src | |
| parent | start on extracting libraries (diff) | |
extracting natives
Diffstat (limited to 'src')
| -rw-r--r-- | src/launcher.rs | 70 | ||||
| -rw-r--r-- | src/launcher/extract.rs | 105 |
2 files changed, 144 insertions, 31 deletions
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) +} |
