diff options
Diffstat (limited to 'src/launcher/jre.rs')
| -rw-r--r-- | src/launcher/jre.rs | 330 |
1 files changed, 0 insertions, 330 deletions
diff --git a/src/launcher/jre.rs b/src/launcher/jre.rs deleted file mode 100644 index 31034b5..0000000 --- a/src/launcher/jre.rs +++ /dev/null @@ -1,330 +0,0 @@ -use std::error::Error; -use std::fmt::{Debug, Display, Formatter}; -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}; - -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, IntegrityError}; -use crate::version::DownloadInfo; -use super::constants; - -pub struct JavaRuntimeRepository { - online: bool, - home: PathBuf, - manifest: JavaRuntimesManifest -} - -impl JavaRuntimeRepository { - pub async fn new(home: impl AsRef<Path>, online: bool) -> Result<Self, JavaRuntimeError> { - info!("Java runtime architecture is \"{}\".", JRE_ARCH); - - fs::create_dir_all(&home).await.map_err(|e| JavaRuntimeError::IO { what: "creating home directory", error: e })?; - - let manifest_path = home.as_ref().join("manifest.json"); - match util::ensure_file(manifest_path.as_path(), Some(constants::URL_JRE_MANIFEST), None, None, online, true).await { - Ok(_) => (), - Err(EnsureFileError::Offline) => { - info!("Launcher is offline, cannot download runtime manifest."); - }, - Err(e) => return Err(JavaRuntimeError::EnsureFile(e)) - }; - - let manifest_file = fs::read_to_string(&manifest_path).await - .map_err(|e| JavaRuntimeError::IO { what: "reading runtimes manifest", error: e })?; - - Ok(JavaRuntimeRepository { - online, - home: home.as_ref().to_path_buf(), - manifest: serde_json::from_str(&manifest_file).map_err(|e| JavaRuntimeError::Deserialize { what: "runtimes manifest", error: e })?, - }) - } - - fn get_component_dir(&self, component: &str) -> PathBuf { - [self.home.as_path(), Path::new(JRE_ARCH), Path::new(component)].into_iter().collect() - } - - async fn load_runtime_manifest(&self, component: &str, info: &DownloadInfo) -> Result<JavaRuntimeManifest, JavaRuntimeError> { - let comp_dir = self.get_component_dir(component); - let manifest_path = comp_dir.join("manifest.json"); - - debug!("Ensuring manifest for runtime {JRE_ARCH}.{component}"); - - fs::create_dir_all(comp_dir.as_path()).await - .inspect_err(|e| warn!("Failed to create directory for JRE component {}: {}", component, e)) - .map_err(|e| JavaRuntimeError::IO { what: "creating component directory", error: e })?; - - util::ensure_file(&manifest_path, info.url.as_deref(), info.size, info.sha1, self.online, false).await - .map_err(JavaRuntimeError::EnsureFile)?; - - let manifest_file = fs::read_to_string(&manifest_path).await - .map_err(|e| JavaRuntimeError::IO { what: "reading runtimes manifest", error: e })?; - - serde_json::from_str(&manifest_file).map_err(|e| JavaRuntimeError::Deserialize { what: "runtime manifest", error: e }) - } - - // not very descriptive function name - pub async fn choose_runtime(&self, component: &str) -> Result<JavaRuntimeManifest, JavaRuntimeError> { - let Some(runtime_components) = self.manifest.get(JRE_ARCH) else { - return Err(JavaRuntimeError::UnsupportedArch(JRE_ARCH)); - }; - - let Some(runtime_component) = runtime_components.get(component) else { - return Err(JavaRuntimeError::UnsupportedComponent { arch: JRE_ARCH, component: component.to_owned() }); - }; - - let Some(runtime) = runtime_component.iter().find(|r| r.availability.progress == 100) else { - if !runtime_components.is_empty() { - warn!("Weird: the only java runtimes in {JRE_ARCH}.{component} has a progress of less than 100!"); - } - - return Err(JavaRuntimeError::UnsupportedComponent { arch: JRE_ARCH, component: component.to_owned() }); - }; - - self.load_runtime_manifest(component, &runtime.manifest).await - } - - 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().any(|c| !matches!(&c, Component::CurDir)) { - // if this path is trivial (points at the root), ignore it - continue; - } - - let rel_path_str = if std::path::MAIN_SEPARATOR != '/' { - rel_path.to_str().map(|s| s.replace(std::path::MAIN_SEPARATOR, "/")) - } else { - rel_path.to_str().map(String::from) - }; - - if !rel_path_str.as_ref().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()); - - if entry.file_type().is_dir() { - std::fs::remove_dir(entry.path())?; - } else { - std::fs::remove_file(entry.path())?; - } - } - } - - Ok(()) - } - - async fn clean_up_runtime(path: &Path, manifest: Arc<JavaRuntimeManifest>) -> Result<(), io::Error> { - let (tx, rx) = tokio::sync::oneshot::channel(); - - let path = path.to_owned(); - let manifest = manifest.clone(); - - tokio::task::spawn_blocking(move || { - let res = Self::clean_up_runtime_sync(&path, manifest); - let _ = tx.send(res); - }).await.expect("clean_up_runtime_sync panicked"); - - rx.await.expect("clean_up_runtime_sync hung up") - } - - async fn ensure_jre_dirs(&self, path: &Path, manifest: &JavaRuntimeManifest) -> Result<(), JavaRuntimeError> { - stream::iter(manifest.files.iter().filter(|(_, f)| f.is_directory())) - .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>(); - - match fs::metadata(&ent_path).await { - Ok(meta) => { - if !meta.is_dir() { - debug!("Deleting misplaced file at {}", ent_path.display()); - fs::remove_file(&ent_path).await.map_err(|e| JavaRuntimeError::IO { - what: "deleting misplaced file", - error: e - })?; - } - }, - Err(e) if e.kind() == ErrorKind::NotFound => (), - Err(e) => return Err(JavaRuntimeError::IO { what: "'stat'ing directory", error: e }) - } - - match fs::create_dir(&ent_path).await { - Ok(_) => { - debug!("Created directory at {}", ent_path.display()); - Ok(()) - }, - Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()), - Err(e) => { - warn!("Could not create directory {} for JRE!", ent_path.display()); - Err(JavaRuntimeError::IO { what: "creating directory", error: e }) - } - } - }).await - } - - async fn ensure_jre_files(path: &Path, manifest: &JavaRuntimeManifest) -> 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>(); - - 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 - })?; - - Ok(()) - })) - .try_buffer_unordered(32) - .try_fold((), |_, _| async { Ok(()) }).await - } - - 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); - - fs::create_dir_all(&runtime_path).await - .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.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?; - - Ok(runtime_path) - } -} - -#[derive(Debug)] -pub enum JavaRuntimeError { - EnsureFile(EnsureFileError), - IO { what: &'static str, error: io::Error }, - Download { what: &'static str, error: reqwest::Error }, - Deserialize { what: &'static str, error: serde_json::Error }, - UnsupportedArch(&'static str), - UnsupportedComponent { arch: &'static str, component: String }, - MalformedManifest(&'static str), - Integrity(IntegrityError), - MultiDownloadError -} - -impl Display for JavaRuntimeError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - JavaRuntimeError::EnsureFile(e) => std::fmt::Display::fmt(e, f), - JavaRuntimeError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error), - JavaRuntimeError::Download { what, error } => write!(f, "error downloading {}: {}", what, error), - JavaRuntimeError::Deserialize { what, error } => write!(f, "error deserializing ({what}): {error}"), - 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::MultiDownloadError => f.write_str("error in multi downloader (see logs for more details)") - } - } -} - -impl Error for JavaRuntimeError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - JavaRuntimeError::EnsureFile(error) => Some(error), - JavaRuntimeError::IO { error, .. } => Some(error), - JavaRuntimeError::Download { error, .. } => Some(error), - JavaRuntimeError::Deserialize { error, .. } => Some(error), - JavaRuntimeError::Integrity(error) => Some(error), - _ => None - } - } -} |
