use crate::error::InstallationResult;
use crate::minecraft::install::InstallationUpdate;
use crate::minecraft::models::AssetInfo;
use crate::minecraft::InstallOptions;
use crate::utils::{download, download_progress_channel, Download, DownloadProgress};
use futures::future::try_join_all;
use futures::join;
use std::path::Path;
use tokio::fs::{self, create_dir_all, File};
use tokio::io::AsyncWriteExt;
use tokio::sync::mpsc::{Receiver, Sender};
#[instrument(
name = "install_assets",
level = "trace",
skip_all,
fields(
version = &options.version_data.id,
assets_path = %options.assets_path.display(),
minecraft_path = %options.minecraft_path.display(),
map_to_resources = &options.asset_index.map_to_resources,
parallel_downloads = options.parallel_downloads,
download_retries = options.download_retries,
verify_downloads = options.verify_downloads,
)
)]
pub async fn install_assets(
options: &InstallOptions,
update_sender: Sender<InstallationUpdate>,
) -> InstallationResult<()> {
trace!("Building downloads for assets");
let downloads = options
.asset_index
.objects
.values()
.map(|a| build_download(a, &options.assets_path))
.collect::<Result<Vec<_>, _>>()?;
trace!("Preparing futures for downloading and channel translation");
let (tx, rx) = download_progress_channel(500);
let download_future = download(
downloads,
Some(tx),
options.parallel_downloads,
options.download_retries,
options.verify_downloads,
);
let map_future = map_progress(update_sender.clone(), rx);
trace!("Starting downloads");
join!(download_future, map_future).0?;
if options.asset_index.map_to_resources {
trace!("Preparing mapping to resources");
let symlinks = options.asset_index.objects.iter().map(|(key, asset)| {
create_symlink(
key,
asset,
&options.assets_path,
&options.minecraft_path,
&update_sender,
)
});
trace!("Starting creating symlinks");
try_join_all(symlinks).await?;
}
trace!("Saving asset index to disk");
let asset_index_json = serde_json::to_vec_pretty(&options.asset_index)?;
let mut asset_index_path = options.assets_path.clone();
asset_index_path.push("indexes");
create_dir_all(&asset_index_path).await?;
asset_index_path.push(format!("{}.json", &options.version_data.assets));
let mut file = File::create(asset_index_path).await?;
file.write_all(&asset_index_json).await?;
file.sync_all().await?;
Ok(())
}
#[instrument(
name = "create_asset_symlink",
level = "trace",
skip_all,
fields(
asset = key,
assets_path,
minecraft_path,
)
)]
async fn create_symlink(
key: &str,
asset: &AssetInfo,
assets_path: impl AsRef<Path> + Send,
minecraft_path: impl AsRef<Path> + Send,
update_sender: &Sender<InstallationUpdate>,
) -> InstallationResult<()> {
trace!("Sending progress");
let name = asset
.asset_path(&assets_path)
.file_name()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_default();
update_sender
.send(InstallationUpdate::Asset((
name,
AssetInstallationUpdate::Symlink,
)))
.await
.ok();
let asset_path = asset.asset_path(assets_path);
let resource_path = AssetInfo::resource_path(key, &minecraft_path);
let parent_dir = resource_path.parent().unwrap();
trace!("Creating parent directory for symlink");
fs::create_dir_all(parent_dir).await?;
trace!(
"Creating symlink: {} => {}",
resource_path.to_string_lossy(),
asset_path.to_string_lossy()
);
#[cfg(unix)]
fs::symlink(asset_path, resource_path).await?;
#[cfg(windows)]
fs::symlink_file(asset_path, resource_path).await?;
Ok(())
}
async fn map_progress(
sender: Sender<InstallationUpdate>,
mut receiver: Receiver<DownloadProgress>,
) {
while let Some(p) = receiver.recv().await {
let name = p
.file
.file_name()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_default();
let send_result = sender
.send(InstallationUpdate::Asset((
name,
AssetInstallationUpdate::Downloading(p),
)))
.await;
if send_result.is_err() {
debug!("Failed to translate DownloadProgress to InstallationUpdate");
break;
}
}
}
fn build_download(
asset: &AssetInfo,
assets_path: impl AsRef<Path>,
) -> InstallationResult<Download> {
let sha1 = hex::decode(&asset.hash)?;
Ok(Download {
url: asset.download_url(),
file: asset.asset_path(assets_path),
sha1: Some(sha1),
})
}
#[derive(Clone, Debug)]
pub enum AssetInstallationUpdate {
Downloading(DownloadProgress),
Symlink,
}