You've already forked AstralRinth
forked from didirus/AstralRinth
Initial draft of profile metadata format & CLI (#17)
* Initial draft of profile metadata format * Remove records, add Clippy to Nix, fix Clippy error * Work on profile definition * BREAKING: Make global settings consistent with profile settings * Add builder methods & format * Integrate launching with profiles * Add profile loading * Launching via profile, API tweaks, and yak shaving * Incremental update, committing everything due to personal system maintainance * Prepare for review cycle * Remove reminents of experimental work * CLI: allow people to override the non-empty directory check * Fix mistake in previous commit * Handle trailing whitespace and newlines in prompts * Revamp prompt to use dialoguer and support defaults * Make requested changes
This commit is contained in:
@@ -1,15 +1,38 @@
|
||||
use crate::launcher::LauncherError;
|
||||
use crate::{
|
||||
data::{DataError, Settings},
|
||||
launcher::LauncherError,
|
||||
};
|
||||
use daedalus::get_path_from_artifact;
|
||||
use daedalus::minecraft::{
|
||||
fetch_assets_index, fetch_version_info, Asset, AssetsIndex, DownloadType, Library, Os, Version,
|
||||
VersionInfo,
|
||||
fetch_assets_index, fetch_version_info, Asset, AssetsIndex, DownloadType,
|
||||
Library, Os, Version, VersionInfo,
|
||||
};
|
||||
use daedalus::modded::{
|
||||
fetch_partial_version, merge_partial_version, LoaderVersion,
|
||||
};
|
||||
use daedalus::modded::{fetch_partial_version, merge_partial_version, LoaderVersion};
|
||||
use futures::future;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use tokio::{
|
||||
fs::File,
|
||||
io::AsyncWriteExt,
|
||||
sync::{OnceCell, Semaphore},
|
||||
};
|
||||
|
||||
static DOWNLOADS_SEMAPHORE: OnceCell<Semaphore> = OnceCell::const_new();
|
||||
|
||||
pub async fn init() -> Result<(), DataError> {
|
||||
DOWNLOADS_SEMAPHORE
|
||||
.get_or_try_init(|| async {
|
||||
let settings = Settings::get().await?;
|
||||
Ok::<_, DataError>(Semaphore::new(
|
||||
settings.max_concurrent_downloads,
|
||||
))
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn download_version_info(
|
||||
client_path: &Path,
|
||||
@@ -22,8 +45,7 @@ pub async fn download_version_info(
|
||||
};
|
||||
|
||||
let mut path = client_path.join(id);
|
||||
path.push(id);
|
||||
path.set_extension("json");
|
||||
path.push(&format!("{id}.json"));
|
||||
|
||||
if path.exists() {
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
@@ -37,7 +59,7 @@ pub async fn download_version_info(
|
||||
info.id = loader_version.id.clone();
|
||||
}
|
||||
let info_s = serde_json::to_string(&info)?;
|
||||
save_file(&path, &bytes::Bytes::from(info_s))?;
|
||||
save_file(&path, &bytes::Bytes::from(info_s)).await?;
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
@@ -58,10 +80,14 @@ pub async fn download_client(
|
||||
})?;
|
||||
|
||||
let mut path = client_path.join(version);
|
||||
path.push(version);
|
||||
path.set_extension("jar");
|
||||
path.push(&format!("{version}.jar"));
|
||||
|
||||
save_and_download_file(&path, &client_download.url, Some(&client_download.sha1)).await?;
|
||||
save_and_download_file(
|
||||
&path,
|
||||
&client_download.url,
|
||||
Some(&client_download.sha1),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -69,7 +95,8 @@ pub async fn download_assets_index(
|
||||
assets_path: &Path,
|
||||
version: &VersionInfo,
|
||||
) -> Result<AssetsIndex, LauncherError> {
|
||||
let path = assets_path.join(format!("indexes/{}.json", &version.asset_index.id));
|
||||
let path =
|
||||
assets_path.join(format!("indexes/{}.json", &version.asset_index.id));
|
||||
|
||||
if path.exists() {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
@@ -77,7 +104,8 @@ pub async fn download_assets_index(
|
||||
} else {
|
||||
let index = fetch_assets_index(version).await?;
|
||||
|
||||
save_file(&path, &bytes::Bytes::from(serde_json::to_string(&index)?))?;
|
||||
save_file(&path, &bytes::Bytes::from(serde_json::to_string(&index)?))
|
||||
.await?;
|
||||
|
||||
Ok(index)
|
||||
}
|
||||
@@ -88,12 +116,9 @@ pub async fn download_assets(
|
||||
legacy_path: Option<&Path>,
|
||||
index: &AssetsIndex,
|
||||
) -> Result<(), LauncherError> {
|
||||
future::join_all(
|
||||
index
|
||||
.objects
|
||||
.iter()
|
||||
.map(|(name, asset)| download_asset(assets_path, legacy_path, name, asset)),
|
||||
)
|
||||
future::join_all(index.objects.iter().map(|(name, asset)| {
|
||||
download_asset(assets_path, legacy_path, name, asset)
|
||||
}))
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<()>, LauncherError>>()?;
|
||||
@@ -114,14 +139,16 @@ async fn download_asset(
|
||||
resource_path.push(sub_hash);
|
||||
resource_path.push(hash);
|
||||
|
||||
let url = format!("https://resources.download.minecraft.net/{sub_hash}/{hash}");
|
||||
let url =
|
||||
format!("https://resources.download.minecraft.net/{sub_hash}/{hash}");
|
||||
|
||||
let resource = save_and_download_file(&resource_path, &url, Some(hash)).await?;
|
||||
let resource =
|
||||
save_and_download_file(&resource_path, &url, Some(hash)).await?;
|
||||
|
||||
if let Some(legacy_path) = legacy_path {
|
||||
let resource_path =
|
||||
legacy_path.join(name.replace('/', &std::path::MAIN_SEPARATOR.to_string()));
|
||||
save_file(resource_path.as_path(), &resource)?;
|
||||
let resource_path = legacy_path
|
||||
.join(name.replace('/', &std::path::MAIN_SEPARATOR.to_string()));
|
||||
save_file(resource_path.as_path(), &resource).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -132,11 +159,9 @@ pub async fn download_libraries(
|
||||
natives_path: &Path,
|
||||
libraries: &[Library],
|
||||
) -> Result<(), LauncherError> {
|
||||
future::join_all(
|
||||
libraries
|
||||
.iter()
|
||||
.map(|library| download_library(libraries_path, natives_path, library)),
|
||||
)
|
||||
future::join_all(libraries.iter().map(|library| {
|
||||
download_library(libraries_path, natives_path, library)
|
||||
}))
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<()>, LauncherError>>()?;
|
||||
@@ -173,7 +198,8 @@ async fn download_library_jar(
|
||||
|
||||
if let Some(downloads) = &library.downloads {
|
||||
if let Some(library) = &downloads.artifact {
|
||||
save_and_download_file(&path, &library.url, Some(&library.sha1)).await?;
|
||||
save_and_download_file(&path, &library.url, Some(&library.sha1))
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
let url = format!(
|
||||
@@ -189,16 +215,21 @@ async fn download_library_jar(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn download_native(natives_path: &Path, library: &Library) -> Result<(), LauncherError> {
|
||||
async fn download_native(
|
||||
natives_path: &Path,
|
||||
library: &Library,
|
||||
) -> Result<(), LauncherError> {
|
||||
use daedalus::minecraft::LibraryDownload;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Try blocks in stable Rust when?
|
||||
let optional_cascade = || -> Option<(&String, &HashMap<String, LibraryDownload>)> {
|
||||
let os_key = library.natives.as_ref()?.get(&get_os())?;
|
||||
let classifiers = library.downloads.as_ref()?.classifiers.as_ref()?;
|
||||
Some((os_key, classifiers))
|
||||
};
|
||||
let optional_cascade =
|
||||
|| -> Option<(&String, &HashMap<String, LibraryDownload>)> {
|
||||
let os_key = library.natives.as_ref()?.get(&get_os())?;
|
||||
let classifiers =
|
||||
library.downloads.as_ref()?.classifiers.as_ref()?;
|
||||
Some((os_key, classifiers))
|
||||
};
|
||||
|
||||
if let Some((os_key, classifiers)) = optional_cascade() {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
@@ -227,19 +258,25 @@ async fn save_and_download_file(
|
||||
Ok(bytes) => Ok(bytes::Bytes::from(bytes)),
|
||||
Err(_) => {
|
||||
let file = download_file(url, sha1).await?;
|
||||
save_file(path, &file)?;
|
||||
save_file(path, &file).await?;
|
||||
Ok(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn save_file(path: &Path, bytes: &bytes::Bytes) -> std::io::Result<()> {
|
||||
async fn save_file(path: &Path, bytes: &bytes::Bytes) -> std::io::Result<()> {
|
||||
let _save_permit = DOWNLOADS_SEMAPHORE
|
||||
.get()
|
||||
.expect("File operation semaphore not initialized!")
|
||||
.acquire()
|
||||
.await
|
||||
.unwrap();
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
|
||||
let mut file = File::create(path)?;
|
||||
file.write_all(bytes)?;
|
||||
let mut file = File::create(path).await?;
|
||||
file.write_all(bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -252,7 +289,17 @@ pub fn get_os() -> Os {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn download_file(url: &str, sha1: Option<&str>) -> Result<bytes::Bytes, LauncherError> {
|
||||
pub async fn download_file(
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
) -> Result<bytes::Bytes, LauncherError> {
|
||||
let _download_permit = DOWNLOADS_SEMAPHORE
|
||||
.get()
|
||||
.expect("File operation semaphore not initialized!")
|
||||
.acquire()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.tcp_keepalive(Some(Duration::from_secs(10)))
|
||||
.build()
|
||||
@@ -307,7 +354,9 @@ pub async fn download_file(url: &str, sha1: Option<&str>) -> Result<bytes::Bytes
|
||||
|
||||
/// Computes a checksum of the input bytes
|
||||
async fn get_hash(bytes: bytes::Bytes) -> Result<String, LauncherError> {
|
||||
let hash = tokio::task::spawn_blocking(|| sha1::Sha1::from(bytes).hexdigest()).await?;
|
||||
let hash =
|
||||
tokio::task::spawn_blocking(|| sha1::Sha1::from(bytes).hexdigest())
|
||||
.await?;
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user