diff options
| author | 2025-01-14 02:51:02 -0600 | |
|---|---|---|
| committer | 2025-01-14 02:51:02 -0600 | |
| commit | 7a42235af574a3a8f07db8ccc3189ce735213ce8 (patch) | |
| tree | 275b230cb93ed70ee7b0aadd1e5e1dc7da9eccef /src/launcher/download.rs | |
| parent | library downloads complete (diff) | |
verify libraries when offline
Diffstat (limited to 'src/launcher/download.rs')
| -rw-r--r-- | src/launcher/download.rs | 116 |
1 files changed, 42 insertions, 74 deletions
diff --git a/src/launcher/download.rs b/src/launcher/download.rs index 813117c..7d9be73 100644 --- a/src/launcher/download.rs +++ b/src/launcher/download.rs @@ -2,14 +2,16 @@ use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use std::io::ErrorKind; use std::path::{Path, PathBuf}; -use futures::{stream, Stream, StreamExt}; -use log::debug; +use futures::{stream, StreamExt, TryStream}; +use log::{debug, warn}; use reqwest::{Client, IntoUrl, Method, RequestBuilder}; use sha1_smol::{Digest, Sha1}; use tokio::fs; use tokio::fs::File; -use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; +use tokio::io::{self, AsyncWriteExt}; use crate::launcher::constants::USER_AGENT; +use crate::util; +use crate::util::{FileVerifyError, IntegrityError}; pub trait Download: Debug + Display { // return Ok(None) to skip downloading this file @@ -99,7 +101,7 @@ impl<T: Download> MultiDownloader<T> { } } - pub async fn perform(&mut self) -> impl Stream<Item = Result<(), PhaseDownloadError<T>>> { + pub async fn perform(&mut self) -> impl TryStream<Ok = (), Error = PhaseDownloadError<T>> { stream::iter(self.jobs.iter_mut()).map(|job| { let client = &self.client; @@ -135,25 +137,6 @@ impl<T: Download> MultiDownloader<T> { } } -#[derive(Debug)] -pub enum IntegrityError { - SizeMismatch{ expect: usize, actual: usize }, - Sha1Mismatch{ expect: Digest, actual: Digest } -} - -impl Display for IntegrityError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - IntegrityError::SizeMismatch{ expect, actual } => - write!(f, "size mismatch (expect {expect} bytes, got {actual} bytes)"), - IntegrityError::Sha1Mismatch {expect, actual} => - write!(f, "sha1 mismatch (expect {expect}, got {actual})") - } - } -} - -impl Error for IntegrityError {} - pub struct VerifiedDownload { url: String, expect_size: Option<usize>, @@ -177,7 +160,7 @@ impl Debug for VerifiedDownload { impl Display for VerifiedDownload { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "downloading {} to {}", self.url, self.path.to_string_lossy()) + write!(f, "downloading {} to {}", self.url, self.path.display()) } } @@ -206,10 +189,22 @@ impl VerifiedDownload { self } + pub fn get_url(&self) -> &str { + &self.url + } + pub fn get_path(&self) -> &Path { &self.path } + pub fn get_expect_size(&self) -> Option<usize> { + self.expect_size + } + + pub fn get_expect_sha1(&self) -> Option<Digest> { + self.expect_sha1 + } + pub async fn make_dirs(&self) -> Result<(), io::Error> { fs::create_dir_all(self.path.parent().expect("download created with no containing directory (?)")).await } @@ -226,59 +221,32 @@ impl Download for VerifiedDownload { } async fn prepare(&mut self, req: RequestBuilder) -> Result<Option<RequestBuilder>, Box<dyn Error>> { - let mut file = match File::open(&self.path).await { - Ok(file) => file, - Err(e) => return if e.kind() == ErrorKind::NotFound { - // assume the parent folder exists (responsibility of the caller to ensure this) - debug!("File {} does not exist, downloading it.", self.path.to_string_lossy()); - self.open_output().await?; - Ok(Some(req)) - } else { - debug!("Error opening {}: {}", self.path.to_string_lossy(), e); - Err(e.into()) + match util::verify_file(&self.path, self.expect_size, self.expect_sha1).await { + Ok(()) => { + debug!("Skipping download for file {}, integrity matches.", self.path.display()); + return Ok(None); + }, + Err(e) => match e { + FileVerifyError::Integrity(_, _) => { + warn!("Integrity error on library: {}", e); + + // try to delete the file since it's bad + let _ = fs::remove_file(&self.path).await + .map_err(|e| warn!("Error deleting corrupted/modified file {} (ignoring): {}", self.path.display(), e)); + }, + FileVerifyError::Open(_, e) => match e.kind() { + ErrorKind::NotFound => { + debug!("File {} is missing, downloading it.", self.path.display()); + }, + _ => return Err(e.into()) + }, + _ => return Err(e.into()) } - }; - - // short-circuit this - if self.expect_size.is_none() && self.expect_sha1.is_none() { - debug!("No size or sha1 for {}, have to assume it's good.", self.path.to_string_lossy()); - return Ok(None); } - let mut tally = 0usize; - let mut sha1 = Sha1::new(); - - let mut buf = [0u8; 4096]; - loop { - let n = match file.read(&mut buf).await { - Ok(n) => n, - Err(e) => match e.kind() { - ErrorKind::Interrupted => continue, - _ => { - debug!("Error reading {}: {}", self.path.to_string_lossy(), e); - return Err(e.into()); - } - } - }; - - if n == 0 { break; } - - tally += n; - sha1.update(&buf[..n]); - } - - if self.expect_sha1.is_none_or(|d| d == sha1.digest()) - && self.expect_size.is_none_or(|s| s == tally) { - debug!("Not downloading {}, sha1 and size match.", self.path.to_string_lossy()); - return Ok(None); - } - - drop(file); - // potentially racy to close the file and reopen it... :/ self.open_output().await?; - debug!("Downloading {} because sha1 or size does not match.", self.path.to_string_lossy()); Ok(Some(req)) } @@ -295,17 +263,17 @@ impl Download for VerifiedDownload { if let Some(d) = self.expect_sha1 { if d != digest { - debug!("Could not download {}: sha1 mismatch (exp {}, got {}).", self.path.to_string_lossy(), d, digest); + debug!("Could not download {}: sha1 mismatch (exp {}, got {}).", self.path.display(), d, digest); return Err(IntegrityError::Sha1Mismatch { expect: d, actual: digest }.into()); } } else if let Some(s) = self.expect_size { if s != self.tally { - debug!("Could not download {}: size mismatch (exp {}, got {}).", self.path.to_string_lossy(), s, self.tally); + debug!("Could not download {}: size mismatch (exp {}, got {}).", self.path.display(), s, self.tally); return Err(IntegrityError::SizeMismatch { expect: s, actual: self.tally }.into()); } } - debug!("Successfully downloaded {} ({} bytes).", self.path.to_string_lossy(), self.tally); + debug!("Successfully downloaded {} ({} bytes).", self.path.display(), self.tally); // release the file descriptor (don't want to wait until it's dropped automatically because idk when that would be) drop(self.file.take().unwrap()); |
