summaryrefslogtreecommitdiffstats
path: root/src/util.rs
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-01-22 02:08:31 -0600
committerLibravatar bigfoot547 <[email protected]>2025-01-22 02:10:20 -0600
commite88c17a44c94f788e945c5728bc18beca7e0f8a6 (patch)
tree586873a545cb6bc799129219d5e809106daa58c1 /src/util.rs
parentsupport 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.rs122
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);