diff options
Diffstat (limited to 'src/launcher')
| -rw-r--r-- | src/launcher/version.rs | 105 |
1 files changed, 71 insertions, 34 deletions
diff --git a/src/launcher/version.rs b/src/launcher/version.rs index 49525b0..0f55223 100644 --- a/src/launcher/version.rs +++ b/src/launcher/version.rs @@ -13,17 +13,51 @@ use crate::version::{*, manifest::*}; use super::constants::*; +#[derive(Debug)] +pub enum VersionError { + IO { what: String, error: io::Error }, + Request { what: String, error: reqwest::Error }, + MalformedObject { what: String, error: serde_json::Error }, + VersionIntegrity { id: String, expect: Digest, got: Digest } +} + +impl Display for VersionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + VersionError::IO { what, error } => write!(f, "i/o error ({what}): {error}"), + VersionError::Request { what, error } => write!(f, "request error ({what}): {error}"), + VersionError::MalformedObject { what, error } => write!(f, "malformed {what}: {error}"), + VersionError::VersionIntegrity { id, expect, got } => write!(f, "version {id} integrity mismatch (expect {expect}, got {got})") + } + } +} + +impl Error for VersionError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + VersionError::IO { error, .. } => Some(error), + VersionError::Request { error, .. } => Some(error), + VersionError::MalformedObject { error, .. } => Some(error), + _ => None + } + } +} + struct RemoteVersionList { versions: HashMap<String, VersionManifestVersion>, latest: LatestVersions } impl RemoteVersionList { - async fn new() -> Result<RemoteVersionList, Box<dyn Error>> { + async fn new() -> Result<RemoteVersionList, VersionError> { debug!("Looking up remote version manifest."); - let text = reqwest::get(URL_VERSION_MANIFEST).await?.error_for_status()?.text().await?; + let text = reqwest::get(URL_VERSION_MANIFEST).await + .and_then(|r| r.error_for_status()) + .map_err(|e| VersionError::Request { what: "download version manifest".into(), error: e })? + .text().await.map_err(|e| VersionError::Request { what: "download version manifest (decode)".into(), error: e })?; + debug!("Parsing version manifest."); - let manifest: VersionManifest = serde_json::from_str(text.as_str())?; + let manifest: VersionManifest = serde_json::from_str(text.as_str()).map_err(|e| VersionError::MalformedObject { what: "version manifest".into(), error: e })?; let mut versions = HashMap::new(); for v in manifest.versions { @@ -37,32 +71,37 @@ impl RemoteVersionList { }) } - async fn download_version(&self, ver: &VersionManifestVersion, path: &Path) -> Result<CompleteVersion, Box<dyn Error>> { + async fn download_version(&self, ver: &VersionManifestVersion, path: &Path) -> Result<CompleteVersion, VersionError> { // ensure parent directory exists info!("Downloading version {}.", ver.id); - if let Err(e) = tokio::fs::create_dir_all(path.parent().expect("version .json has no parent (impossible)")).await { - warn!("failed to create {} parent dirs: {e}", path.display()); - return Err(e.into()); - } + tokio::fs::create_dir_all(path.parent().expect("version .json has no parent (impossible)")).await + .inspect_err(|e| warn!("failed to create {} parent dirs: {e}", path.display())) + .map_err(|e| VersionError::IO { what: format!("creating version directory for {}", path.display()), error: e })?; // download it - let ver_text = reqwest::get(ver.url.as_str()).await?.error_for_status()?.text().await?; - + let ver_text = reqwest::get(ver.url.as_str()).await + .and_then(|r| r.error_for_status()) + .map_err(|e| VersionError::Request { what: format!("download version {} from {}", ver.id, ver.url), error: e })? + .text().await.map_err(|e| VersionError::Request { what: format!("download version {} from {} (receive)", ver.id, ver.url), error: e })?; + 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())?; + .map_err(|e| VersionError::VersionIntegrity { + id: ver.id.clone(), + expect: ver.sha1, + got: e + })?; // make sure it's well-formed - let cver: CompleteVersion = serde_json::from_str(ver_text.as_str())?; + let cver: CompleteVersion = serde_json::from_str(ver_text.as_str()).map_err(|e| VersionError::MalformedObject { what: format!("complete version {}", ver.id), error: e })?; debug!("Saving version {}...", ver.id); // write it out - tokio::fs::write(path, ver_text).await.map_err(|e| { - warn!("Failed to save version {}: {}", ver.id, e); - e - })?; + tokio::fs::write(path, ver_text).await + .inspect_err(|e| warn!("Failed to save version {}: {}", ver.id, e)) + .map_err(|e| VersionError::IO { what: format!("writing version file at {}", path.display()), error: e })?; info!("Done downloading and verifying {}!", ver.id); @@ -132,13 +171,13 @@ impl LocalVersionList { } } - async fn load_versions(home: &Path, skip: impl Fn(&str) -> bool) -> Result<LocalVersionList, Box<dyn Error>> { + async fn load_versions(home: &Path, skip: impl Fn(&str) -> bool) -> Result<LocalVersionList, VersionError> { info!("Loading local versions."); - let mut rd = tokio::fs::read_dir(home).await?; + let mut rd = tokio::fs::read_dir(home).await.map_err(|e| VersionError::IO { what: format!("open local versions directory {}", home.display()), error: e })?; let mut versions = BTreeMap::new(); - while let Some(ent) = rd.next_entry().await? { - if !ent.file_type().await?.is_dir() { continue; } + while let Some(ent) = rd.next_entry().await.map_err(|e| VersionError::IO { what: format!("read local versions directory {}", home.display()), error: e })? { + if !ent.file_type().await.map_err(|e| VersionError::IO { what: format!("version entry metadata {}", ent.path().display()), error: e} )?.is_dir() { continue; } // when the code is fugly let path = match ent.file_name().to_str() { @@ -215,7 +254,7 @@ impl<'a, T: Into<VersionResult<'a>>> From<Option<T>> for VersionResult<'a> { pub enum VersionResolveError { InheritanceLoop(String), MissingVersion(String), - Unknown(Box<dyn Error>) + VersionLoad(VersionError) } impl Display for VersionResolveError { @@ -223,7 +262,7 @@ impl Display for VersionResolveError { 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::VersionLoad(err) => write!(f, "version load error: {err}") } } } @@ -235,18 +274,16 @@ impl VersionList { 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) - } + Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()), + Err(e) => { + 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?; + pub async fn online(home: &Path) -> Result<VersionList, VersionError> { + Self::create_dir_for(home).await.map_err(|e| VersionError::IO { what: format!("create version directory {}", home.display()), error: e })?; let remote = RemoteVersionList::new().await?; let local = LocalVersionList::load_versions(home, |s| remote.versions.contains_key(s)).await?; @@ -258,8 +295,8 @@ impl VersionList { }) } - pub async fn offline(home: &Path) -> Result<VersionList, Box<dyn Error>> { - Self::create_dir_for(home).await?; + pub async fn offline(home: &Path) -> Result<VersionList, VersionError> { + Self::create_dir_for(home).await.map_err(|e| VersionError::IO { what: format!("create version directory {}", home.display()), error: e })?; let local = LocalVersionList::load_versions(home, |_| false).await?; @@ -294,7 +331,7 @@ impl VersionList { remote.versions.get(id) } - pub async fn load_remote_version(&self, ver: &VersionManifestVersion) -> Result<CompleteVersion, Box<dyn Error>> { + pub async fn load_remote_version(&self, ver: &VersionManifestVersion) -> Result<CompleteVersion, VersionError> { let remote = self.remote.as_ref().expect("load_remote_version called in offline mode!"); let id = ver.id.as_str(); @@ -340,7 +377,7 @@ impl VersionList { let inherited_ver = match self.get_version_lazy(inherit.as_str()) { VersionResult::Complete(v) => Cow::Borrowed(v), VersionResult::Remote(v) => - Cow::Owned(self.load_remote_version(v).await.map_err(VersionResolveError::Unknown)?), + Cow::Owned(self.load_remote_version(v).await.map_err(VersionResolveError::VersionLoad)?), VersionResult::None => { warn!("Cannot resolve version {}, it inherits an unknown version {inherit}", ver.id); return Err(VersionResolveError::MissingVersion(inherit)); |
