diff options
| author | 2025-01-22 02:08:31 -0600 | |
|---|---|---|
| committer | 2025-01-22 02:10:20 -0600 | |
| commit | e88c17a44c94f788e945c5728bc18beca7e0f8a6 (patch) | |
| tree | 586873a545cb6bc799129219d5e809106daa58c1 /src/util.rs | |
| parent | support jre specified in profile (diff) | |
get started on downloading JREs
also refactor ensure file logic
Diffstat (limited to 'src/util.rs')
| -rw-r--r-- | src/util.rs | 122 |
1 files changed, 117 insertions, 5 deletions
diff --git a/src/util.rs b/src/util.rs index 7927620..1184a83 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,13 +1,13 @@ use std::error::Error; -use std::ffi::OsStr; use std::fmt::{Display, Formatter}; use std::io::ErrorKind; -use std::path::{Component, Path, PathBuf, Prefix}; -use log::debug; +use std::path::{Component, Path, PathBuf}; +use log::{debug, info, warn}; use sha1_smol::{Digest, Sha1}; use tokio::fs::File; -use tokio::io::AsyncReadExt; -use crate::util; +use tokio::{fs, io}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use crate::launcher::LaunchError; #[derive(Debug)] pub enum IntegrityError { @@ -113,6 +113,118 @@ pub async fn verify_file(path: impl AsRef<Path>, expect_size: Option<usize>, exp Ok(()) } +#[derive(Debug)] +pub enum EnsureFileError { + IO { what: &'static str, error: io::Error }, + Download { url: String, error: reqwest::Error }, + Integrity(IntegrityError), + Offline, + MissingURL +} + +impl Display for EnsureFileError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + EnsureFileError::IO { what, error } => write!(f, "i/o error ensuring file ({what}): {error}"), + EnsureFileError::Download { url, error } => write!(f, "error downloading file ({url}): {error}"), + EnsureFileError::Integrity(e) => write!(f, "integrity error for downloaded file: {e}"), + EnsureFileError::Offline => f.write_str("unable to download file while offline"), + EnsureFileError::MissingURL => f.write_str("missing url"), + } + } +} + +impl Error for EnsureFileError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + EnsureFileError::IO { error, .. } => Some(error), + EnsureFileError::Download { error, .. } => Some(error), + EnsureFileError::Integrity(error) => Some(error), + _ => None + } + } +} + +pub async fn ensure_file(path: impl AsRef<Path>, url: Option<&str>, expect_size: Option<usize>, expect_sha1: Option<Digest>, online: bool) -> Result<bool, EnsureFileError> { + let path = path.as_ref(); + + match verify_file(path, expect_size, expect_sha1).await { + Ok(_) => { + info!("File {} exists and integrity matches. Skipping.", path.display()); + return Ok(false); + }, + Err(FileVerifyError::Open(_, e)) if e.kind() == ErrorKind::NotFound => (), + Err(FileVerifyError::Integrity(_, e)) => + info!("File {} on disk failed integrity check: {}", path.display(), e), + Err(FileVerifyError::Open(_, e)) | Err(FileVerifyError::Read(_, e)) => + return Err(EnsureFileError::IO { what: "verifying fileon disk", error: e }) + } + + if !online { + warn!("Cannot download {} to {} while offline!", url.unwrap_or("(no url)"), path.display()); + return Err(EnsureFileError::Offline); + } + + // download the file + let Some(url) = url else { + return Err(EnsureFileError::MissingURL); + }; + + let mut file = File::create(path).await.map_err(|e| EnsureFileError::IO { + what: "save downloaded file (open)", + error: e + })?; + + debug!("File {} must be downloaded ({}).", path.display(), url); + + let mut response = reqwest::get(url).await.map_err(|e| EnsureFileError::Download { url: url.to_owned(), error: e })?; + let mut tally = 0usize; + let mut sha1 = Sha1::new(); + + while let Some(chunk) = response.chunk().await.map_err(|e| EnsureFileError::Download { url: url.to_owned(), error: e })? { + let slice = chunk.as_ref(); + + file.write_all(slice).await.map_err(|e| EnsureFileError::IO { + what: "save downloaded file (write)", + error: e + })?; + + tally += slice.len(); + sha1.update(slice); + } + + drop(file); // manually close file + + let del_file_silent = || async { + debug!("Deleting downloaded file {} since its integrity doesn't match :(", path.display()); + let _ = fs::remove_file(path).await.map_err(|e| warn!("failed to delete invalid downloaded file: {}", e)); + () + }; + + if expect_size.is_some_and(|s| s != tally) { + del_file_silent().await; + + return Err(EnsureFileError::Integrity(IntegrityError::SizeMismatch { + expect: expect_size.unwrap(), + actual: tally + })); + } + + let digest = sha1.digest(); + + if expect_sha1.is_some_and(|exp_dig| exp_dig != digest) { + del_file_silent().await; + + return Err(EnsureFileError::Integrity(IntegrityError::Sha1Mismatch { + expect: expect_sha1.unwrap(), + actual: digest + })); + } + + info!("File {} downloaded successfully.", path.display()); + Ok(true) +} + pub fn check_path(name: &str) -> Result<&Path, &'static str> { let entry_path: &Path = Path::new(name); |
