summaryrefslogtreecommitdiffstats
path: root/src/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'src/launcher')
-rw-r--r--src/launcher/download.rs116
-rw-r--r--src/launcher/version.rs4
2 files changed, 44 insertions, 76 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());
diff --git a/src/launcher/version.rs b/src/launcher/version.rs
index 40bb953..2e320f2 100644
--- a/src/launcher/version.rs
+++ b/src/launcher/version.rs
@@ -4,7 +4,7 @@ use std::collections::HashSet;
use std::fmt::Display;
use std::path::{Path, PathBuf};
-use log::{debug, info, trace, warn};
+use log::{debug, info, warn};
use sha1_smol::Digest;
use tokio::{fs, io};
use crate::util;
@@ -42,7 +42,7 @@ impl RemoteVersionList {
match tokio::fs::create_dir_all(path.parent().expect("version .json has no parent (impossible)")).await {
Err(e) => {
if e.kind() != ErrorKind::AlreadyExists {
- warn!("failed to create {} parent dirs: {e}", path.to_string_lossy());
+ warn!("failed to create {} parent dirs: {e}", path.display());
return Err(e.into());
}
},