diff options
Diffstat (limited to 'src/launcher/version.rs')
| -rw-r--r-- | src/launcher/version.rs | 70 |
1 files changed, 58 insertions, 12 deletions
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)); } |
