From b2edba152d0256a8921f3a25d67a062163a54f59 Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Wed, 22 Jan 2025 19:03:31 -0600 Subject: finish downloaded jres --- src/launcher/jre/download.rs | 211 +++++++++++++++++++++++++++++++++++++++++++ src/launcher/jre/manifest.rs | 4 +- 2 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 src/launcher/jre/download.rs (limited to 'src/launcher/jre') diff --git a/src/launcher/jre/download.rs b/src/launcher/jre/download.rs new file mode 100644 index 0000000..d8631aa --- /dev/null +++ b/src/launcher/jre/download.rs @@ -0,0 +1,211 @@ +use std::error::Error; +use std::fmt::{Debug, Display, Formatter}; +use std::fs::Permissions; +use std::io::Write; +use std::ops::AddAssign; +use std::path::{Path, PathBuf}; +use log::debug; +use lzma_rs::decompress; +use reqwest::{IntoUrl, RequestBuilder}; +use sha1_smol::{Digest, Sha1}; +use tokio::fs; +use tokio::io::AsyncWriteExt; +use tokio::fs::File; +use crate::launcher::download::Download; +use crate::launcher::jre::manifest::JavaRuntimeFile; +use crate::util; +use crate::util::IntegrityError; +use crate::version::DownloadInfo; + +pub enum LzmaDownloadError { + NotAFile, + MissingURL +} + +pub struct LzmaDownloadJob { + url: String, + path: PathBuf, + inflate: bool, + executable: bool, + + raw_size: Option, + raw_sha1: Option, + + com_size: Option, + com_sha1: Option, + + raw_sha1_st: Sha1, + raw_tally: usize, + + stream: Option>>, + out_file: Option +} + +impl LzmaDownloadJob { + fn new_inflate(raw: &DownloadInfo, lzma: &DownloadInfo, exe: bool, path: PathBuf) -> Result { + Ok(LzmaDownloadJob { + url: lzma.url.as_ref().map_or_else(|| Err(LzmaDownloadError::MissingURL), |u| Ok(u.to_owned()))?, + path, + inflate: true, + executable: exe, + + raw_size: raw.size, + raw_sha1: raw.sha1, + + com_size: lzma.size, + com_sha1: lzma.sha1, + + raw_sha1_st: Sha1::new(), + raw_tally: 0, + + stream: Some(decompress::Stream::new(Vec::new())), + out_file: None + }) + } + + fn new_raw(raw: &DownloadInfo, exe: bool, path: PathBuf) -> Result { + Ok(LzmaDownloadJob { + url: raw.url.as_ref().map_or_else(|| Err(LzmaDownloadError::MissingURL), |u| Ok(u.to_owned()))?, + path, + inflate: false, + executable: exe, + + raw_size: raw.size, + raw_sha1: raw.sha1, + + com_size: None, + com_sha1: None, + + raw_sha1_st: Sha1::new(), + raw_tally: 0, + + stream: None, + out_file: None + }) + } +} + +impl TryFrom<(&JavaRuntimeFile, PathBuf)> for LzmaDownloadJob { + type Error = LzmaDownloadError; + + fn try_from((file, path): (&JavaRuntimeFile, PathBuf)) -> Result { + if !file.is_file() { + return Err(LzmaDownloadError::NotAFile); + } + + let JavaRuntimeFile::File { executable, downloads } = file else { + unreachable!("we just made sure this was a file"); + }; + + match downloads.lzma.as_ref() { + Some(lzma) => LzmaDownloadJob::new_inflate(&downloads.raw, lzma, *executable, path), + None => LzmaDownloadJob::new_raw(&downloads.raw, *executable, path) + } + } +} + +impl Debug for LzmaDownloadJob { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LzmaDownloadJob") + .field("url", &self.url) + .field("path", &self.path) + .field("inflate", &self.inflate) + .finish() + } +} + +impl Display for LzmaDownloadJob { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.inflate { + write!(f, "download and inflate {} to {}", &self.url, self.path.display()) + } else { + write!(f, "download {} to {}", &self.url, self.path.display()) + } + } +} + +impl Download for LzmaDownloadJob { + fn get_url(&self) -> impl IntoUrl { + self.url.as_str() + } + + async fn prepare(&mut self, req: RequestBuilder) -> Result, Box> { + if !util::should_download(&self.path, self.raw_size, self.raw_sha1).await? { + return Ok(None) + } + + let mut options = File::options(); + + #[cfg(unix)] + { + options.mode(match self.executable { + true => 0o775, + _ => 0o664 + }); + } + + let file = options.create(true).write(true).truncate(true).open(&self.path).await?; + self.out_file = Some(file); + + Ok(Some(req)) + } + + async fn handle_chunk(&mut self, chunk: &[u8]) -> Result<(), Box> { + let out_file = self.out_file.as_mut().expect("output file gone"); + + if let Some(ref mut stream) = self.stream { + stream.write_all(chunk)?; + let buf = stream.get_output_mut().expect("stream output missing before finish()"); + + out_file.write_all(buf.as_slice()).await?; + + self.raw_sha1_st.update(buf.as_slice()); + self.raw_tally += buf.len(); + + buf.truncate(0); + } else { + out_file.write_all(chunk).await?; + + self.raw_sha1_st.update(chunk); + self.raw_tally += chunk.len(); + } + + Ok(()) + } + + async fn finish(&mut self) -> Result<(), Box> { + let mut out_file = self.out_file.take().expect("output file gone"); + + if let Some(stream) = self.stream.take() { + let buf = stream.finish()?; + + out_file.write_all(buf.as_slice()).await?; + + self.raw_sha1_st.update(buf.as_slice()); + self.raw_tally += buf.len(); + } + + let inf_digest = self.raw_sha1_st.digest(); + if let Some(sha1) = self.raw_sha1 { + if inf_digest != sha1 { + debug!("Could not download {}: sha1 mismatch (exp {}, got {}).", self.path.display(), sha1, inf_digest); + return Err(IntegrityError::Sha1Mismatch { + expect: sha1, + actual: inf_digest + }.into()); + } + } + + if let Some(size) = self.raw_size { + if self.raw_tally != size { + debug!("Could not download {}: size mismatch (exp {}, got {}).", self.path.display(), size, self.raw_tally); + return Err(IntegrityError::SizeMismatch { + expect: size, + actual: self.raw_tally + }.into()); + } + } + + Ok(()) + } +} diff --git a/src/launcher/jre/manifest.rs b/src/launcher/jre/manifest.rs index 41780d0..887871a 100644 --- a/src/launcher/jre/manifest.rs +++ b/src/launcher/jre/manifest.rs @@ -27,8 +27,8 @@ pub type JavaRuntimesManifest = HashMap, - raw: DownloadInfo + pub lzma: Option, + pub raw: DownloadInfo } #[derive(Debug, Deserialize)] -- cgit v1.2.3-70-g09d2