summaryrefslogtreecommitdiffstats
path: root/src/launcher
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-01-12 03:58:01 -0600
committerLibravatar bigfoot547 <[email protected]>2025-01-12 03:58:01 -0600
commitc0986823af246ccee2247b881974a2b7ce6ee491 (patch)
tree9dcb8a66692d5c0067450c60e1de72bd4fce92a5 /src/launcher
parentuse a macro for map_err (diff)
add some logging and stuff
Diffstat (limited to 'src/launcher')
-rw-r--r--src/launcher/download.rs25
-rw-r--r--src/launcher/strsub.rs28
-rw-r--r--src/launcher/version.rs70
3 files changed, 100 insertions, 23 deletions
diff --git a/src/launcher/download.rs b/src/launcher/download.rs
index 28c3b86..8c24cae 100644
--- a/src/launcher/download.rs
+++ b/src/launcher/download.rs
@@ -3,6 +3,7 @@ use std::fmt::{Debug, Display, Formatter};
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use futures::{stream, Stream, StreamExt};
+use log::debug;
use reqwest::{Client, IntoUrl, Method, RequestBuilder};
use sha1_smol::{Digest, Sha1};
use tokio::fs;
@@ -101,7 +102,7 @@ impl<T: Download> MultiDownloader<T> {
pub async fn perform(&mut self) -> impl Stream<Item = Result<(), PhaseDownloadError<T>>> {
stream::iter(self.jobs.iter_mut()).map(|job| {
let client = &self.client;
-
+
macro_rules! map_err {
($result:expr, $phase:expr, $job:expr) => {
match $result {
@@ -117,7 +118,7 @@ impl<T: Download> MultiDownloader<T> {
.header(reqwest::header::USER_AGENT, USER_AGENT)).await, Phase::Prepare, job) else {
return Ok(())
};
-
+
let mut data = map_err!(map_err!(rq.send().await, Phase::Send, job).error_for_status(), Phase::Send, job).bytes_stream();
while let Some(bytes) = data.next().await {
@@ -220,13 +221,21 @@ impl Download for VerifiedDownload {
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())
}
};
+ // 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();
@@ -236,7 +245,10 @@ impl Download for VerifiedDownload {
Ok(n) => n,
Err(e) => match e.kind() {
ErrorKind::Interrupted => continue,
- _ => return Err(e.into())
+ _ => {
+ debug!("Error reading {}: {}", self.path.to_string_lossy(), e);
+ return Err(e.into());
+ }
}
};
@@ -248,6 +260,7 @@ impl Download for VerifiedDownload {
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);
}
@@ -255,6 +268,8 @@ impl Download for VerifiedDownload {
// 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))
}
@@ -271,13 +286,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);
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);
return Err(IntegrityError::SizeMismatch { expect: s, actual: self.tally }.into());
}
}
+
+ debug!("Successfully downloaded {} ({} bytes).", self.path.to_string_lossy(), 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/strsub.rs b/src/launcher/strsub.rs
index cdf395b..e01ef80 100644
--- a/src/launcher/strsub.rs
+++ b/src/launcher/strsub.rs
@@ -12,23 +12,35 @@ fn prev_char(slice: &str, idx: usize) -> Option<(usize, char)> {
slice[..idx].char_indices().rev().next()
}
-// basically the same thing as replace_string, but it creates the String itself and returns it.
-pub fn replace_str<'rep, T>(input: &str, sub: T) -> String
+pub trait SubFunc<'rep>: Fn(&str) -> Option<Cow<'rep, str>> {
+ fn substitute(&self, key: &str) -> Option<Cow<'rep, str>>;
+}
+
+impl<'rep, F> SubFunc<'rep> for F
where
- T: Fn(/*key: */ &str) -> Option<Cow<'rep, str>>
+ F: Fn(&str) -> Option<Cow<'rep, str>>
{
+ fn substitute(&self, key: &str) -> Option<Cow<'rep, str>> {
+ self(key)
+ }
+}
+
+// basically the same thing as replace_string, but it creates the String itself and returns it.
+pub fn replace_str<'rep>(input: &str, sub: impl SubFunc<'rep>) -> String {
let mut input = String::from(input);
replace_string(&mut input, sub);
input
}
+pub fn replace_thru<'rep>(mut input: String, sub: impl SubFunc<'rep>) -> String {
+ replace_string(&mut input, sub);
+ input
+}
+
// handles ${replacements} on this string IN-PLACE. Calls the "sub" function for each key it receives.
// if "sub" returns None, it will use a default value or ignore the ${substitution}.
// There are no "invalid inputs" and this function should never panic unless "sub" panics.
-pub fn replace_string<'rep, T>(input: &mut String, sub: T)
-where
- T: Fn(/*key: */ &str) -> Option<Cow<'rep, str>>
-{
+pub fn replace_string<'rep>(input: &mut String, sub: impl SubFunc<'rep>) {
let mut cursor = input.len();
while let Some(idx) = input[..cursor].rfind(VAR_BEGIN) {
// note: for some reason, apache processes escapes BEFORE checking if it's even a valid
@@ -58,7 +70,7 @@ where
def_opt = None;
}
- if let Some(sub_val) = sub(name).map_or_else(|| def_opt.map(|d| Cow::Owned(d.to_owned())), |v| Some(v)) {
+ if let Some(sub_val) = sub.substitute(name).map_or_else(|| def_opt.map(|d| Cow::Owned(d.to_owned())), |v| Some(v)) {
input.replace_range(idx..(endidx + VAR_END.len()), sub_val.as_ref());
}
diff --git a/src/launcher/version.rs b/src/launcher/version.rs
index f4cdd6c..411ac59 100644
--- a/src/launcher/version.rs
+++ b/src/launcher/version.rs
@@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
use log::{debug, info, warn};
use sha1_smol::Digest;
+use tokio::{fs, io};
use crate::util;
use crate::version::{*, manifest::*};
@@ -18,7 +19,9 @@ struct RemoteVersionList {
impl RemoteVersionList {
async fn new() -> Result<RemoteVersionList, Box<dyn Error>> {
+ debug!("Looking up remote version manifest.");
let text = reqwest::get(URL_VERSION_MANIFEST).await?.error_for_status()?.text().await?;
+ debug!("Parsing version manifest.");
let manifest: VersionManifest = serde_json::from_str(text.as_str())?;
let mut versions = HashMap::new();
@@ -26,6 +29,7 @@ impl RemoteVersionList {
versions.insert(v.id.clone(), v);
}
+ debug!("Done loading remote versions!");
Ok(RemoteVersionList {
versions,
latest: manifest.latest
@@ -34,6 +38,7 @@ impl RemoteVersionList {
async fn download_version(&self, ver: &VersionManifestVersion, path: &Path) -> Result<CompleteVersion, Box<dyn Error>> {
// ensure parent directory exists
+ info!("Downloading version {}.", ver.id);
match tokio::fs::create_dir_all(path.parent().expect("version .json has no parent (impossible)")).await {
Err(e) => {
if e.kind() != ErrorKind::AlreadyExists {
@@ -47,6 +52,7 @@ impl RemoteVersionList {
// download it
let ver_text = reqwest::get(ver.url.as_str()).await?.error_for_status()?.text().await?;
+ debug!("Validating downloaded {}...", ver.id);
// make sure it's valid
util::verify_sha1(ver.sha1, ver_text.as_str())
.map_err::<Box<dyn Error>, _>(|e| format!("downloaded version {} has wrong hash! (expect {}, got {})", ver.id.as_str(), &ver.sha1, e).as_str().into())?;
@@ -54,9 +60,13 @@ impl RemoteVersionList {
// make sure it's well-formed
let cver: CompleteVersion = serde_json::from_str(ver.url.as_str())?;
+ debug!("Saving version {}...", ver.id);
+
// write it out
tokio::fs::write(path, ver_text).await?;
+ info!("Done downloading and verifying {}!", ver.id);
+
Ok(cver)
}
}
@@ -93,13 +103,21 @@ impl Error for LocalVersionError {}
impl LocalVersionList {
async fn load_version(path: &Path, sha1: Option<Digest>) -> Result<CompleteVersion, LocalVersionError> {
// grumble grumble I don't like reading in the whole file at once
+ info!("Loading local version at {}.", path.display());
let ver = tokio::fs::read_to_string(path).await.map_err(|e| LocalVersionError::Unknown(Box::new(e)))?;
if let Some(digest_exp) = sha1 {
+ debug!("Verifying local version {}.", path.display());
util::verify_sha1(digest_exp, ver.as_str())
- .map_err(|got| LocalVersionError::Sha1Mismatch { exp: digest_exp.to_owned(), got })?;
+ .map_err(|got| {
+ warn!("Local version sha1 mismatch: {} (exp: {}, got: {})", path.display(), digest_exp, got);
+ LocalVersionError::Sha1Mismatch { exp: digest_exp.to_owned(), got }
+ })?;
}
- let ver: CompleteVersion = serde_json::from_str(ver.as_str()).map_err(|e| LocalVersionError::Unknown(Box::new(e)))?;
+ let ver: CompleteVersion = serde_json::from_str(ver.as_str()).map_err(|e| {
+ warn!("Invalid version JSON {}: {}", path.display(), e);
+ LocalVersionError::Unknown(Box::new(e))
+ })?;
let fname_id = path.file_stem()
.expect("tried to load a local version with no path") // should be impossible
@@ -107,13 +125,16 @@ impl LocalVersionList {
.expect("tried to load a local version with invalid UTF-8 filename"); // we already checked if the filename is valid UTF-8 at this point
if fname_id == ver.id.as_str() {
+ info!("Loaded local version {}.", ver.id);
Ok(ver)
} else {
+ warn!("Local version {} has a version ID conflict (filename: {}, json: {})!", path.display(), fname_id, ver.id);
Err(LocalVersionError::VersionMismatch { fname: fname_id.to_owned(), json: ver.id })
}
}
async fn load_versions(home: &Path, skip: impl Fn(&str) -> bool) -> Result<LocalVersionList, Box<dyn Error>> {
+ info!("Loading local versions.");
let mut rd = tokio::fs::read_dir(home).await?;
let mut versions = BTreeMap::new();
@@ -156,6 +177,7 @@ impl LocalVersionList {
}
}
+ info!("Loaded {} local version(s).", versions.len());
Ok(LocalVersionList { versions })
}
}
@@ -200,23 +222,35 @@ pub enum VersionResolveError {
impl Display for VersionResolveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- VersionResolveError::InheritanceLoop(s) => {
- write!(f, "inheritance loop (saw {s} twice)")
- },
- VersionResolveError::MissingVersion(s) => {
- write!(f, "unknown version {s}")
- },
- VersionResolveError::Unknown(err) => {
- write!(f, "unknown error: {err}")
- }
+ VersionResolveError::InheritanceLoop(s) => write!(f, "inheritance loop (saw {s} twice)"),
+ VersionResolveError::MissingVersion(s) => write!(f, "unknown version {s}"),
+ VersionResolveError::Unknown(err) => write!(f, "unknown error: {err}")
}
}
}
impl Error for VersionResolveError {}
+
+
impl VersionList {
+ async fn create_dir_for(home: &Path) -> Result<(), io::Error> {
+ debug!("Creating versions directory.");
+ match fs::create_dir(home).await {
+ Ok(_) => Ok(()),
+ Err(e) => match e.kind() {
+ ErrorKind::AlreadyExists => Ok(()),
+ _ => {
+ debug!("failed to create version home: {}", e);
+ Err(e)
+ }
+ }
+ }
+ }
+
pub async fn online(home: &Path) -> Result<VersionList, Box<dyn Error>> {
+ Self::create_dir_for(home).await?;
+
let remote = RemoteVersionList::new().await?;
let local = LocalVersionList::load_versions(home.as_ref(), |s| remote.versions.contains_key(s)).await?;
@@ -228,6 +262,8 @@ impl VersionList {
}
pub async fn offline(home: &Path) -> Result<VersionList, Box<dyn Error>> {
+ Self::create_dir_for(home).await?;
+
let local = LocalVersionList::load_versions(home, |_| false).await?;
Ok(VersionList {
@@ -259,10 +295,12 @@ impl VersionList {
let mut ver_path = self.home.join(id);
ver_path.push(format!("{id}.json"));
+ debug!("Loading local copy of remote version {}", ver.id);
+
match LocalVersionList::load_version(ver_path.as_path(), Some(ver.sha1)).await {
Ok(v) => return Ok(v),
Err(e) => {
- info!("redownloading {id}, since the local copy could not be loaded: {e}");
+ info!("Redownloading {id}, since the local copy could not be loaded: {e}");
}
}
@@ -277,11 +315,19 @@ impl VersionList {
return Ok(Cow::Borrowed(ver));
};
+ if *inherit == ver.id {
+ warn!("Version {} directly inherits from itself!", ver.id);
+ return Err(VersionResolveError::InheritanceLoop(ver.id.clone()));
+ }
+
+ debug!("Resolving version inheritance: {} (inherits from {})", ver.id, inherit);
+
let mut ver = ver.clone();
let mut inherit = inherit.clone();
loop {
if seen.insert(inherit.clone()) {
+ warn!("Version inheritance loop detected in {}: {} transitively inherits from itself.", ver.id, inherit);
return Err(VersionResolveError::InheritanceLoop(inherit));
}