From cdeee17c2be5b8b9a333b977b3e2d373b94dfe0a Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Fri, 31 Jan 2025 02:32:19 -0600 Subject: do clippy stuff and change line endings --- src/launcher/assets.rs | 644 ++++++++++++++++++++++++------------------------- 1 file changed, 322 insertions(+), 322 deletions(-) (limited to 'src/launcher/assets.rs') diff --git a/src/launcher/assets.rs b/src/launcher/assets.rs index dacd01d..7c5dcf3 100644 --- a/src/launcher/assets.rs +++ b/src/launcher/assets.rs @@ -1,322 +1,322 @@ -use std::error::Error; -use std::ffi::OsStr; -use std::fmt::{Display, Formatter}; -use std::io::ErrorKind; -use std::path::{Path, PathBuf}; -use std::path::Component::Normal; -use futures::{stream, TryStreamExt}; -use log::{debug, info, warn}; -use reqwest::Client; -use sha1_smol::Sha1; -use tokio::{fs, io}; -use tokio::fs::File; -use crate::assets::{Asset, AssetIndex}; -use crate::launcher::download::{MultiDownloader, VerifiedDownload}; -use crate::util; -use crate::util::{FileVerifyError, IntegrityError}; -use crate::version::DownloadInfo; - -const INDEX_PATH: &'static str = "indexes"; -const OBJECT_PATH: &'static str = "objects"; - -pub struct AssetRepository { - online: bool, - home: PathBuf -} - -#[derive(Debug)] -pub enum AssetError { - InvalidId(Option), - IO { what: &'static str, error: io::Error }, - IndexParse(serde_json::Error), - Offline, - MissingURL, - DownloadIndex(reqwest::Error), - Integrity(IntegrityError), - AssetObjectDownload, - AssetVerifyError(FileVerifyError), - AssetNameError(&'static str) -} - -impl Display for AssetError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - AssetError::InvalidId(None) => f.write_str("missing asset index id"), - AssetError::InvalidId(Some(id)) => write!(f, "invalid asset index id: {}", id), - AssetError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error), - AssetError::IndexParse(error) => write!(f, "error parsing asset index: {}", error), - AssetError::Offline => f.write_str("cannot download asset index while offline"), - AssetError::MissingURL => f.write_str("missing asset index URL"), - AssetError::DownloadIndex(e) => write!(f, "error downloading asset index: {}", e), - AssetError::Integrity(e) => write!(f, "asset index integrity error: {}", e), - AssetError::AssetObjectDownload => f.write_str("asset object download failed"), - AssetError::AssetVerifyError(e) => write!(f, "error verifying asset object: {e}"), - AssetError::AssetNameError(e) => write!(f, "invalid asset name: {e}") - } - } -} - -impl Error for AssetError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - AssetError::IO { error, .. } => Some(error), - AssetError::IndexParse(error) => Some(error), - AssetError::DownloadIndex(error) => Some(error), - AssetError::Integrity(error) => Some(error), - AssetError::AssetVerifyError(error) => Some(error), - _ => None - } - } -} - -impl From<(&'static str, io::Error)> for AssetError { - fn from((what, error): (&'static str, io::Error)) -> Self { - AssetError::IO { what, error } - } -} - -impl AssetRepository { - pub async fn new(online: bool, home: impl AsRef) -> Result { - let home = home.as_ref().to_owned(); - - match fs::create_dir_all(&home).await { - Ok(_) => (), - Err(e) => match e.kind() { - ErrorKind::AlreadyExists => (), - _ => return Err(e) - } - }; - - Ok(AssetRepository { - online, - home - }) - } - - pub fn get_home(&self) -> &Path { - self.home.as_path() - } - - fn get_index_path(&self, id: &str) -> Result { - let mut indexes_path: PathBuf = [self.home.as_ref(), OsStr::new(INDEX_PATH)].iter().collect(); - let Some(Normal(path)) = Path::new(id).components().last() else { - return Err(AssetError::InvalidId(Some(id.into()))); - }; - - let path = path.to_str().ok_or(AssetError::InvalidId(Some(path.to_string_lossy().into())))?; - - // FIXME: change this once "add_extension" is stabilized - indexes_path.push(format!("{}.json", path)); - - Ok(indexes_path) - } - - pub async fn load_index(&self, index: &DownloadInfo, id: Option<&str>) -> Result { - let Some(id) = id else { - return Err(AssetError::InvalidId(None)); - }; - - info!("Loading asset index {}", id); - - let path = self.get_index_path(id)?; - debug!("Asset index {} is located at {}", id, path.display()); - - match util::verify_file(&path, index.size, index.sha1).await { - Ok(_) => { - debug!("Asset index {} verified on disk. Loading it.", id); - let idx_data = fs::read_to_string(&path).await.map_err(|e| AssetError::IO { - what: "reading asset index", - error: e - })?; - - return Ok(serde_json::from_str(&idx_data).map_err(|e| AssetError::IndexParse(e))?); - }, - Err(FileVerifyError::Open(_, e)) => match e.kind() { - ErrorKind::NotFound => { - debug!("Asset index {} not found on disk. Must download it.", id); - }, - _ => return Err(("opening asset index", e).into()) - }, - Err(FileVerifyError::Integrity(_, e)) => { - info!("Asset index {} has mismatched integrity: {}, must download it.", id, e); - let _ = fs::remove_file(&path).await.map_err(|e| warn!("Error deleting modified index {}: {} (ignored)", id, e)); - }, - Err(FileVerifyError::Read(_, e)) => return Err(("reading asset index", e).into()) - } - - if !self.online { - warn!("Must download asset index {}, but the launcher is in offline mode. Please try again in online mode.", id); - return Err(AssetError::Offline); - } - - let Some(url) = index.url.as_ref() else { - return Err(AssetError::MissingURL); - }; - - debug!("Downloading asset index {} from {}", id, url); - - if let Some(parent) = path.parent() { - fs::create_dir_all(parent).await.map_err(|e| AssetError::IO { - what: "creating asset index folder", - error: e - })?; - } - - let idx_text = reqwest::get(url).await - .map_err(|e| AssetError::DownloadIndex(e))? - .text().await - .map_err(|e| AssetError::DownloadIndex(e))?; - - if index.size.is_some_and(|s| s != idx_text.len()) { - return Err(AssetError::Integrity(IntegrityError::SizeMismatch { - expect: index.size.unwrap(), - actual: idx_text.len() - })); - } - - if let Some(expect) = index.sha1 { - let actual = Sha1::from(&idx_text).digest(); - - if actual != expect { - return Err(AssetError::Integrity(IntegrityError::Sha1Mismatch { expect, actual })); - } - } - - debug!("Saving downloaded asset index to {}", path.display()); - fs::write(&path, &idx_text).await.map_err(|e| AssetError::IO { - what: "writing asset index", - error: e - })?; - - Ok(serde_json::from_str(&idx_text).map_err(|e| AssetError::IndexParse(e))?) - } - - fn get_object_url(obj: &Asset) -> String { - format!("{}{:02x}/{}", super::constants::URL_RESOURCE_BASE, obj.hash.bytes()[0], obj.hash) - } - - pub fn get_object_path(&self, obj: &Asset) -> PathBuf { - let hex_digest = obj.hash.to_string(); - [self.home.as_ref(), OsStr::new(OBJECT_PATH), OsStr::new(&hex_digest[..2]), OsStr::new(&hex_digest)].iter().collect() - } - - async fn ensure_dir(path: impl AsRef) -> Result<(), io::Error> { - match fs::create_dir(path).await { - Ok(_) => Ok(()), - Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()), - Err(e) => Err(e) - } - } - - pub async fn ensure_assets(&self, index: &AssetIndex) -> Result<(), AssetError> { - let mut downloads = Vec::new(); - let objects_path = [self.home.as_ref(), OsStr::new(OBJECT_PATH)].iter().collect::(); - - Self::ensure_dir(&objects_path).await.map_err(|e| AssetError::IO { - what: "creating objects directory", - error: e - })?; - - for object in index.objects.values() { - let path = self.get_object_path(object); - - Self::ensure_dir(path.parent().unwrap()).await.map_err(|error| AssetError::IO { error, what: "creating directory for object" })?; - - downloads.push(VerifiedDownload::new(&Self::get_object_url(object), &path, Some(object.size), Some(object.hash))); - } - - if self.online { - info!("Downloading {} asset objects...", downloads.len()); - let client = Client::new(); - MultiDownloader::with_concurrent(downloads.iter_mut(), 32).perform(&client).await - .inspect_err(|e| warn!("asset download failed: {e}")) - .try_fold((), |_, _| async {Ok(())}) - .await - .map_err(|_| AssetError::AssetObjectDownload)?; - } else { - info!("Verifying {} asset objects...", downloads.len()); - super::download::verify_files(downloads.iter_mut()).await.map_err(|e| AssetError::AssetVerifyError(e))?; - } - - Ok(()) - } - - pub async fn reconstruct_assets(&self, index: &AssetIndex, instance_path: &Path, index_id: Option<&str>) -> Result, AssetError> { - let target_path: PathBuf; - let Some(index_id) = index_id else { - return Err(AssetError::InvalidId(None)); - }; - - if index.virtual_assets { - target_path = [self.home.as_ref(), OsStr::new("virtual"), OsStr::new(index_id)].iter().collect(); - } else if index.map_to_resources { - target_path = [instance_path, Path::new("resources")].iter().collect(); - } else { - info!("This asset index does not request a virtual assets folder. Nothing to be done."); - return Ok(None); - } - - info!("Reconstructing virtual assets for {}", index_id); - - fs::create_dir_all(&target_path).await.map_err(|e| AssetError::from(("creating virtual assets directory", e)))?; - - stream::iter(index.objects.values() - .map(|object| { - let obj_path = util::check_path(object.name.as_str()).map_err(AssetError::AssetNameError)?; - let obj_path = target_path.join(obj_path); - - Ok((object, obj_path)) - })) - .try_filter_map(|(object, obj_path)| async move { - match util::verify_file(&obj_path, Some(object.size), Some(object.hash)).await { - Ok(_) => { - debug!("Not copying asset {}, integrity matches.", object.name); - Ok(None) - } - Err(FileVerifyError::Open(_, e)) if e.kind() == ErrorKind::NotFound => { - debug!("Copying asset {}, file does not exist.", object.name); - Ok(Some((object, obj_path))) - }, - Err(FileVerifyError::Integrity(_, e)) => { - debug!("Copying asset {}: {}", object.name, e); - Ok(Some((object, obj_path))) - }, - Err(e) => { - debug!("Error while reconstructing assets: {e}"); - Err(AssetError::AssetVerifyError(e)) - } - } - }) - .try_for_each_concurrent(32, |(object, obj_path)| async move { - if let Some(parent) = obj_path.parent() { - fs::create_dir_all(parent).await - .inspect_err(|e| debug!("Error creating directory for asset object {}: {e}", object.name)) - .map_err(|e| AssetError::from(("creating asset object directory", e)))?; - } - - let mut fromfile = File::open(self.get_object_path(object)).await - .map_err(|e| AssetError::from(("opening source object", e)))?; - let mut tofile = File::create(&obj_path).await - .map_err(|e| AssetError::from(("creating target object", e)))?; - - io::copy(&mut fromfile, &mut tofile).await.map_err(|e| AssetError::from(("copying asset object", e)))?; - debug!("Copied object {} to {}.", object.name, obj_path.display()); - Ok(()) - }).await.map(|_| Some(target_path)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_it() { - let digest_str = "ad1115931887a73cd596300f2c93f84adf39521d"; - assert_eq!(AssetRepository::get_object_url(&Asset { - name: String::from("test"), - hash: digest_str.parse().unwrap(), - size: 0usize - }), "https://resources.download.minecraft.net/ad/ad1115931887a73cd596300f2c93f84adf39521d"); - } -} +use std::error::Error; +use std::ffi::OsStr; +use std::fmt::{Display, Formatter}; +use std::io::ErrorKind; +use std::path::{Path, PathBuf}; +use std::path::Component::Normal; +use futures::{stream, TryStreamExt}; +use log::{debug, info, warn}; +use reqwest::Client; +use sha1_smol::Sha1; +use tokio::{fs, io}; +use tokio::fs::File; +use crate::assets::{Asset, AssetIndex}; +use crate::launcher::download::{MultiDownloader, VerifiedDownload}; +use crate::util; +use crate::util::{FileVerifyError, IntegrityError}; +use crate::version::DownloadInfo; + +const INDEX_PATH: &str = "indexes"; +const OBJECT_PATH: &str = "objects"; + +pub struct AssetRepository { + online: bool, + home: PathBuf +} + +#[derive(Debug)] +pub enum AssetError { + InvalidId(Option), + IO { what: &'static str, error: io::Error }, + IndexParse(serde_json::Error), + Offline, + MissingURL, + DownloadIndex(reqwest::Error), + Integrity(IntegrityError), + AssetObjectDownload, + AssetVerifyError(FileVerifyError), + AssetNameError(&'static str) +} + +impl Display for AssetError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AssetError::InvalidId(None) => f.write_str("missing asset index id"), + AssetError::InvalidId(Some(id)) => write!(f, "invalid asset index id: {}", id), + AssetError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error), + AssetError::IndexParse(error) => write!(f, "error parsing asset index: {}", error), + AssetError::Offline => f.write_str("cannot download asset index while offline"), + AssetError::MissingURL => f.write_str("missing asset index URL"), + AssetError::DownloadIndex(e) => write!(f, "error downloading asset index: {}", e), + AssetError::Integrity(e) => write!(f, "asset index integrity error: {}", e), + AssetError::AssetObjectDownload => f.write_str("asset object download failed"), + AssetError::AssetVerifyError(e) => write!(f, "error verifying asset object: {e}"), + AssetError::AssetNameError(e) => write!(f, "invalid asset name: {e}") + } + } +} + +impl Error for AssetError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + AssetError::IO { error, .. } => Some(error), + AssetError::IndexParse(error) => Some(error), + AssetError::DownloadIndex(error) => Some(error), + AssetError::Integrity(error) => Some(error), + AssetError::AssetVerifyError(error) => Some(error), + _ => None + } + } +} + +impl From<(&'static str, io::Error)> for AssetError { + fn from((what, error): (&'static str, io::Error)) -> Self { + AssetError::IO { what, error } + } +} + +impl AssetRepository { + pub async fn new(online: bool, home: impl AsRef) -> Result { + let home = home.as_ref().to_owned(); + + match fs::create_dir_all(&home).await { + Ok(_) => (), + Err(e) => match e.kind() { + ErrorKind::AlreadyExists => (), + _ => return Err(e) + } + }; + + Ok(AssetRepository { + online, + home + }) + } + + pub fn get_home(&self) -> &Path { + self.home.as_path() + } + + fn get_index_path(&self, id: &str) -> Result { + let mut indexes_path: PathBuf = [self.home.as_ref(), OsStr::new(INDEX_PATH)].iter().collect(); + let Some(Normal(path)) = Path::new(id).components().last() else { + return Err(AssetError::InvalidId(Some(id.into()))); + }; + + let path = path.to_str().ok_or(AssetError::InvalidId(Some(path.to_string_lossy().into())))?; + + // FIXME: change this once "add_extension" is stabilized + indexes_path.push(format!("{}.json", path)); + + Ok(indexes_path) + } + + pub async fn load_index(&self, index: &DownloadInfo, id: Option<&str>) -> Result { + let Some(id) = id else { + return Err(AssetError::InvalidId(None)); + }; + + info!("Loading asset index {}", id); + + let path = self.get_index_path(id)?; + debug!("Asset index {} is located at {}", id, path.display()); + + match util::verify_file(&path, index.size, index.sha1).await { + Ok(_) => { + debug!("Asset index {} verified on disk. Loading it.", id); + let idx_data = fs::read_to_string(&path).await.map_err(|e| AssetError::IO { + what: "reading asset index", + error: e + })?; + + return serde_json::from_str(&idx_data).map_err(AssetError::IndexParse); + }, + Err(FileVerifyError::Open(_, e)) => match e.kind() { + ErrorKind::NotFound => { + debug!("Asset index {} not found on disk. Must download it.", id); + }, + _ => return Err(("opening asset index", e).into()) + }, + Err(FileVerifyError::Integrity(_, e)) => { + info!("Asset index {} has mismatched integrity: {}, must download it.", id, e); + let _ = fs::remove_file(&path).await.map_err(|e| warn!("Error deleting modified index {}: {} (ignored)", id, e)); + }, + Err(FileVerifyError::Read(_, e)) => return Err(("reading asset index", e).into()) + } + + if !self.online { + warn!("Must download asset index {}, but the launcher is in offline mode. Please try again in online mode.", id); + return Err(AssetError::Offline); + } + + let Some(url) = index.url.as_ref() else { + return Err(AssetError::MissingURL); + }; + + debug!("Downloading asset index {} from {}", id, url); + + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).await.map_err(|e| AssetError::IO { + what: "creating asset index folder", + error: e + })?; + } + + let idx_text = reqwest::get(url).await + .map_err(AssetError::DownloadIndex)? + .text().await + .map_err(AssetError::DownloadIndex)?; + + if index.size.is_some_and(|s| s != idx_text.len()) { + return Err(AssetError::Integrity(IntegrityError::SizeMismatch { + expect: index.size.unwrap(), + actual: idx_text.len() + })); + } + + if let Some(expect) = index.sha1 { + let actual = Sha1::from(&idx_text).digest(); + + if actual != expect { + return Err(AssetError::Integrity(IntegrityError::Sha1Mismatch { expect, actual })); + } + } + + debug!("Saving downloaded asset index to {}", path.display()); + fs::write(&path, &idx_text).await.map_err(|e| AssetError::IO { + what: "writing asset index", + error: e + })?; + + serde_json::from_str(&idx_text).map_err(AssetError::IndexParse) + } + + fn get_object_url(obj: &Asset) -> String { + format!("{}{:02x}/{}", super::constants::URL_RESOURCE_BASE, obj.hash.bytes()[0], obj.hash) + } + + pub fn get_object_path(&self, obj: &Asset) -> PathBuf { + let hex_digest = obj.hash.to_string(); + [self.home.as_ref(), OsStr::new(OBJECT_PATH), OsStr::new(&hex_digest[..2]), OsStr::new(&hex_digest)].iter().collect() + } + + async fn ensure_dir(path: impl AsRef) -> Result<(), io::Error> { + match fs::create_dir(path).await { + Ok(_) => Ok(()), + Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()), + Err(e) => Err(e) + } + } + + pub async fn ensure_assets(&self, index: &AssetIndex) -> Result<(), AssetError> { + let mut downloads = Vec::new(); + let objects_path = [self.home.as_ref(), OsStr::new(OBJECT_PATH)].iter().collect::(); + + Self::ensure_dir(&objects_path).await.map_err(|e| AssetError::IO { + what: "creating objects directory", + error: e + })?; + + for object in index.objects.values() { + let path = self.get_object_path(object); + + Self::ensure_dir(path.parent().unwrap()).await.map_err(|error| AssetError::IO { error, what: "creating directory for object" })?; + + downloads.push(VerifiedDownload::new(&Self::get_object_url(object), &path, Some(object.size), Some(object.hash))); + } + + if self.online { + info!("Downloading {} asset objects...", downloads.len()); + let client = Client::new(); + MultiDownloader::with_concurrent(downloads.iter_mut(), 32).perform(&client).await + .inspect_err(|e| warn!("asset download failed: {e}")) + .try_fold((), |_, _| async {Ok(())}) + .await + .map_err(|_| AssetError::AssetObjectDownload)?; + } else { + info!("Verifying {} asset objects...", downloads.len()); + super::download::verify_files(downloads.iter_mut()).await.map_err(AssetError::AssetVerifyError)?; + } + + Ok(()) + } + + pub async fn reconstruct_assets(&self, index: &AssetIndex, instance_path: &Path, index_id: Option<&str>) -> Result, AssetError> { + let target_path: PathBuf; + let Some(index_id) = index_id else { + return Err(AssetError::InvalidId(None)); + }; + + if index.virtual_assets { + target_path = [self.home.as_ref(), OsStr::new("virtual"), OsStr::new(index_id)].iter().collect(); + } else if index.map_to_resources { + target_path = [instance_path, Path::new("resources")].iter().collect(); + } else { + info!("This asset index does not request a virtual assets folder. Nothing to be done."); + return Ok(None); + } + + info!("Reconstructing virtual assets for {}", index_id); + + fs::create_dir_all(&target_path).await.map_err(|e| AssetError::from(("creating virtual assets directory", e)))?; + + stream::iter(index.objects.values() + .map(|object| { + let obj_path = util::check_path(object.name.as_str()).map_err(AssetError::AssetNameError)?; + let obj_path = target_path.join(obj_path); + + Ok((object, obj_path)) + })) + .try_filter_map(|(object, obj_path)| async move { + match util::verify_file(&obj_path, Some(object.size), Some(object.hash)).await { + Ok(_) => { + debug!("Not copying asset {}, integrity matches.", object.name); + Ok(None) + } + Err(FileVerifyError::Open(_, e)) if e.kind() == ErrorKind::NotFound => { + debug!("Copying asset {}, file does not exist.", object.name); + Ok(Some((object, obj_path))) + }, + Err(FileVerifyError::Integrity(_, e)) => { + debug!("Copying asset {}: {}", object.name, e); + Ok(Some((object, obj_path))) + }, + Err(e) => { + debug!("Error while reconstructing assets: {e}"); + Err(AssetError::AssetVerifyError(e)) + } + } + }) + .try_for_each_concurrent(32, |(object, obj_path)| async move { + if let Some(parent) = obj_path.parent() { + fs::create_dir_all(parent).await + .inspect_err(|e| debug!("Error creating directory for asset object {}: {e}", object.name)) + .map_err(|e| AssetError::from(("creating asset object directory", e)))?; + } + + let mut fromfile = File::open(self.get_object_path(object)).await + .map_err(|e| AssetError::from(("opening source object", e)))?; + let mut tofile = File::create(&obj_path).await + .map_err(|e| AssetError::from(("creating target object", e)))?; + + io::copy(&mut fromfile, &mut tofile).await.map_err(|e| AssetError::from(("copying asset object", e)))?; + debug!("Copied object {} to {}.", object.name, obj_path.display()); + Ok(()) + }).await.map(|_| Some(target_path)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_it() { + let digest_str = "ad1115931887a73cd596300f2c93f84adf39521d"; + assert_eq!(AssetRepository::get_object_url(&Asset { + name: String::from("test"), + hash: digest_str.parse().unwrap(), + size: 0usize + }), "https://resources.download.minecraft.net/ad/ad1115931887a73cd596300f2c93f84adf39521d"); + } +} -- cgit v1.2.3-70-g09d2