diff options
Diffstat (limited to 'src/launcher/jre.rs')
| -rw-r--r-- | src/launcher/jre.rs | 126 |
1 files changed, 106 insertions, 20 deletions
diff --git a/src/launcher/jre.rs b/src/launcher/jre.rs index 4be4c7b..a09c8f3 100644 --- a/src/launcher/jre.rs +++ b/src/launcher/jre.rs @@ -1,20 +1,24 @@ use std::collections::HashSet; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; -use std::path::{Path, PathBuf}; +use std::path::{Component, Path, PathBuf}; use std::sync::Arc; use futures::{stream, StreamExt, TryStreamExt}; use log::{debug, info, warn}; +use reqwest::Client; use tokio::{fs, io, io::ErrorKind}; use tokio::fs::File; use tokio::io::AsyncWriteExt; mod arch; mod manifest; +mod download; use arch::JRE_ARCH; use manifest::JavaRuntimesManifest; use manifest::JavaRuntimeManifest; +use crate::launcher::download::MultiDownloader; +use crate::launcher::jre::download::{LzmaDownloadError, LzmaDownloadJob}; use crate::launcher::jre::manifest::JavaRuntimeFile; use crate::util; use crate::util::{EnsureFileError, FileVerifyError, IntegrityError}; @@ -99,13 +103,17 @@ impl JavaRuntimeRepository { fn clean_up_runtime_sync(path: &Path, manifest: Arc<JavaRuntimeManifest>) -> Result<(), io::Error> { for entry in walkdir::WalkDir::new(path).contents_first(true) { let entry = entry?; + let rel_path = entry.path().strip_prefix(path).expect("walkdir escaped root (???)"); + + if rel_path.components().filter(|c| !matches!(c, Component::CurDir)).next().is_none() { + // if this path is trivial (points at the root), ignore it + continue; + } - if !entry.path().strip_prefix(path) - .expect("walkdir escaped root (???)") - .to_str().map_or(None, |s| manifest.files.get(s)) - .is_none_or(|f| (f.is_file() != entry.file_type().is_file()) - || (f.is_directory() != entry.file_type().is_dir()) - || (f.is_link() != entry.file_type().is_symlink())) { + if !rel_path.to_str().is_some_and(|s| manifest.files.get(s) + .is_some_and(|f| (f.is_file() == entry.file_type().is_file()) + || (f.is_directory() == entry.file_type().is_dir()) + || (f.is_link() == entry.file_type().is_symlink()))) { // path is invalid utf-8, extraneous, or of the wrong type debug!("File {} is extraneous or of wrong type ({:?}). Deleting it.", entry.path().display(), entry.file_type()); @@ -136,7 +144,8 @@ impl JavaRuntimeRepository { async fn ensure_jre_dirs(&self, path: &Path, manifest: &JavaRuntimeManifest) -> Result<(), JavaRuntimeError> { stream::iter(manifest.files.iter().filter(|(_, f)| f.is_directory())) - .map::<Result<_, JavaRuntimeError>, _>(|(name, _)| Ok(async move { + .map::<Result<&String, JavaRuntimeError>, _>(|(name, _)| Ok(name)) + .try_for_each(|name| async move { let ent_path = util::check_path(name).map_err(JavaRuntimeError::MalformedManifest)?; let ent_path = [path, ent_path].into_iter().collect::<PathBuf>(); @@ -165,21 +174,92 @@ impl JavaRuntimeRepository { return Err(JavaRuntimeError::IO { what: "creating directory", error: e }); } } - })) - .try_buffer_unordered(32) - .try_fold((), |_, _| async { Ok(()) }).await + }).await } async fn ensure_jre_files(path: &Path, manifest: &JavaRuntimeManifest) -> Result<(), JavaRuntimeError> { - stream::iter(manifest.files.iter().filter(|(_, f)| f.is_file())) - .map(|(name, file)| Ok(async move { + 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>(); + + downloads.push(LzmaDownloadJob::try_from((file, file_path)).map_err(|e| { + match e { + LzmaDownloadError::MissingURL => JavaRuntimeError::MalformedManifest("runtime manifest missing URL"), + LzmaDownloadError::NotAFile => unreachable!("we just made sure this was a file") + } + })?); + } + + let dl = MultiDownloader::new(downloads.iter_mut()); + let client = Client::new(); + + dl.perform(&client).await + .inspect_err(|e| warn!("jre file download failed: {e}")) + .try_fold((), |_, _| async { Ok(()) }) + .await + .map_err(|_| JavaRuntimeError::MultiDownloadError) + } + + async fn ensure_links(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 { + unreachable!(); + }; + + let target_exp = PathBuf::from(target); + + let path = util::check_path(name.as_str()).map_err(JavaRuntimeError::MalformedManifest)?; + let link_path = [root_path, path].into_iter().collect::<PathBuf>(); + + match fs::read_link(&link_path).await { + Ok(target_path) => { + if target_path == target_exp { + debug!("Symbolic link at {} matches! Nothing to be done.", link_path.display()); + return Ok(()) + } - })); + debug!("Symbolic link at {} does not match (exp {}, got {}). Recreating it.", link_path.display(), target_exp.display(), target_path.display()); + fs::remove_file(&link_path).await.map_err(|e| JavaRuntimeError::IO { + what: "deleting bad symlink", + error: e + })?; + } + Err(e) if e.kind() == ErrorKind::NotFound => (), + Err(e) => return Err(JavaRuntimeError::IO { what: "reading jre symlink", error: e }) + } + + debug!("Creating symbolic link at {} to {}", link_path.display(), target_exp.display()); + + let symlink; + #[cfg(unix)] + { + symlink = |targ, path| async { fs::symlink(targ, path).await }; + } + + #[cfg(windows)] + { + symlink = |targ, path| async { fs::symlink_file(targ, path).await }; + } + + #[cfg(not(any(unix, windows)))] + { + symlink = |_, _| async { Ok(()) }; + } + + symlink(target_exp, link_path).await.map_err(|e| JavaRuntimeError::IO { + what: "creating symlink", + error: e + })?; - todo!() + Ok(()) + })) + .try_buffer_unordered(32) + .try_fold((), |_, _| async { Ok(()) }).await } - async fn ensure_jre(&self, component: &str, manifest: JavaRuntimeManifest) -> Result<(), JavaRuntimeError> { + pub async fn ensure_jre(&self, component: &str, manifest: JavaRuntimeManifest) -> Result<PathBuf, JavaRuntimeError> { let runtime_path = self.get_component_dir(component); let runtime_path = runtime_path.join("runtime"); let manifest = Arc::new(manifest); @@ -188,15 +268,19 @@ impl JavaRuntimeRepository { .map_err(|e| JavaRuntimeError::IO { what: "creating runtime directory", error: e })?; debug!("Cleaning up JRE directory for {component}"); - Self::clean_up_runtime(runtime_path.as_path(), manifest).await + 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}"); 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?; + debug!("Ensuring symbolic links for {component}"); + Self::ensure_links(&runtime_path, manifest.as_ref()).await?; - todo!() + Ok(runtime_path) } } @@ -209,7 +293,8 @@ pub enum JavaRuntimeError { UnsupportedArch(&'static str), UnsupportedComponent { arch: &'static str, component: String }, MalformedManifest(&'static str), - Integrity(IntegrityError) + Integrity(IntegrityError), + MultiDownloadError } impl Display for JavaRuntimeError { @@ -222,7 +307,8 @@ impl Display for JavaRuntimeError { JavaRuntimeError::UnsupportedArch(arch) => write!(f, r#"unsupported architecture "{arch}""#), JavaRuntimeError::UnsupportedComponent { arch, component } => write!(f, r#"unsupported component "{component}" for architecture "{arch}""#), JavaRuntimeError::MalformedManifest(what) => write!(f, "malformed runtime manifest: {what} (launcher bug?)"), - JavaRuntimeError::Integrity(e) => std::fmt::Display::fmt(e, f) + JavaRuntimeError::Integrity(e) => std::fmt::Display::fmt(e, f), + JavaRuntimeError::MultiDownloadError => f.write_str("error in multi downloader (see logs for more details)") } } } |
