summaryrefslogtreecommitdiffstats
path: root/src/launcher/jre
diff options
context:
space:
mode:
Diffstat (limited to 'src/launcher/jre')
-rw-r--r--src/launcher/jre/download.rs211
-rw-r--r--src/launcher/jre/manifest.rs4
2 files changed, 213 insertions, 2 deletions
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<usize>,
+ raw_sha1: Option<Digest>,
+
+ com_size: Option<usize>,
+ com_sha1: Option<Digest>,
+
+ raw_sha1_st: Sha1,
+ raw_tally: usize,
+
+ stream: Option<decompress::Stream<Vec<u8>>>,
+ out_file: Option<File>
+}
+
+impl LzmaDownloadJob {
+ fn new_inflate(raw: &DownloadInfo, lzma: &DownloadInfo, exe: bool, path: PathBuf) -> Result<Self, LzmaDownloadError> {
+ 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<Self, LzmaDownloadError> {
+ 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<Self, Self::Error> {
+ 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<Option<RequestBuilder>, Box<dyn Error>> {
+ 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<dyn Error>> {
+ 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<dyn Error>> {
+ 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<String, HashMap<String, Vec<JavaRuntimeI
#[derive(Debug, Deserialize)]
pub struct FileDownloads {
- lzma: Option<DownloadInfo>,
- raw: DownloadInfo
+ pub lzma: Option<DownloadInfo>,
+ pub raw: DownloadInfo
}
#[derive(Debug, Deserialize)]