summaryrefslogtreecommitdiffstats
path: root/src/launcher/assets.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/launcher/assets.rs')
-rw-r--r--src/launcher/assets.rs84
1 files changed, 78 insertions, 6 deletions
diff --git a/src/launcher/assets.rs b/src/launcher/assets.rs
index e540e50..992af2a 100644
--- a/src/launcher/assets.rs
+++ b/src/launcher/assets.rs
@@ -4,11 +4,13 @@ use std::fmt::{Display, Formatter};
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::path::Component::Normal;
-use futures::TryStreamExt;
+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 tokio_stream::StreamExt;
use crate::assets::{Asset, AssetIndex};
use crate::launcher::download::{MultiDownloader, VerifiedDownload};
use crate::util;
@@ -33,7 +35,8 @@ pub enum AssetError {
DownloadIndex(reqwest::Error),
Integrity(IntegrityError),
AssetObjectDownload,
- AssetVerifyError(FileVerifyError)
+ AssetVerifyError(FileVerifyError),
+ AssetNameError(&'static str)
}
impl Display for AssetError {
@@ -48,7 +51,8 @@ impl Display for AssetError {
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::AssetVerifyError(e) => write!(f, "error verifying asset object: {e}"),
+ AssetError::AssetNameError(e) => write!(f, "invalid asset name: {e}")
}
}
}
@@ -105,7 +109,7 @@ impl AssetRepository {
}
pub async fn load_index(&self, index: &DownloadInfo, id: Option<&str>) -> Result<AssetIndex, AssetError> {
- let Some(id) = index.id.as_ref().map(|s| s.as_str()).or(id) else {
+ let Some(id) = id else {
return Err(AssetError::InvalidId(None));
};
@@ -188,6 +192,11 @@ impl AssetRepository {
format!("{}{:02x}/{}", super::constants::URL_RESOURCE_BASE, obj.hash.bytes()[0], obj.hash)
}
+ 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<Path>) -> Result<(), io::Error> {
match fs::create_dir(path).await {
Ok(_) => Ok(()),
@@ -206,8 +215,7 @@ impl AssetRepository {
})?;
for object in index.objects.iter() {
- let hex_digest = object.hash.to_string();
- let path = [objects_path.as_ref(), OsStr::new(&hex_digest[..2]), OsStr::new(&hex_digest)].iter().collect::<PathBuf>();
+ 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" })?;
@@ -229,6 +237,70 @@ impl AssetRepository {
Ok(())
}
+
+ pub async fn reconstruct_assets(&self, index: &AssetIndex, instance_path: &Path, index_id: Option<&str>) -> Result<Option<PathBuf>, 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.iter()
+ .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(16, |(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))
+ }
}
mod tests {