feat: vanilla server downloading + lots of ux improvements
This commit is contained in:
parent
505bfbbb5d
commit
0a12b87a2a
6 changed files with 241 additions and 22 deletions
126
Cargo.lock
generated
126
Cargo.lock
generated
|
|
@ -93,6 +93,15 @@ version = "2.13.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
|
|
@ -217,6 +226,12 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
|
|
@ -252,6 +267,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.12.0"
|
||||
|
|
@ -265,6 +289,17 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.7"
|
||||
|
|
@ -361,6 +396,23 @@ version = "0.3.34"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.34"
|
||||
|
|
@ -380,7 +432,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
|
@ -491,6 +547,15 @@ version = "1.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.11.1"
|
||||
|
|
@ -666,6 +731,19 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.18.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c"
|
||||
dependencies = [
|
||||
"console",
|
||||
"portable-atomic",
|
||||
"unicode-width",
|
||||
"unit-prefix",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.1"
|
||||
|
|
@ -799,8 +877,11 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"dialoguer",
|
||||
"futures-util",
|
||||
"indicatif",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"sha1",
|
||||
"tokio",
|
||||
"toml",
|
||||
]
|
||||
|
|
@ -887,6 +968,12 @@ version = "0.3.34"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.6"
|
||||
|
|
@ -1022,6 +1109,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
|
|
@ -1043,12 +1131,14 @@ dependencies = [
|
|||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
|
|
@ -1280,6 +1370,17 @@ dependencies = [
|
|||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.1"
|
||||
|
|
@ -1649,6 +1750,12 @@ version = "0.2.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
|
|
@ -1661,6 +1768,12 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "unit-prefix"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
|
|
@ -1771,6 +1884,19 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.104"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ edition = "2024"
|
|||
[dependencies]
|
||||
clap = { version = "4.6.6", features = ["derive"] }
|
||||
dialoguer = { version = "0.12.0", features = ["fuzzy-select"] }
|
||||
reqwest = { version = "0.13.4", features = ["json"] }
|
||||
futures-util = "0.3.34"
|
||||
indicatif = "0.18.6"
|
||||
reqwest = { version = "0.13.4", features = ["json", "stream"] }
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
sha1 = "0.11.0"
|
||||
tokio = { version = "1.53.1", features = ["full"] }
|
||||
toml = "1.1.4"
|
||||
|
|
|
|||
BIN
new/server.jar
Normal file
BIN
new/server.jar
Normal file
Binary file not shown.
90
src/main.rs
90
src/main.rs
|
|
@ -1,10 +1,17 @@
|
|||
mod models;
|
||||
mod utils;
|
||||
|
||||
use std::{error::Error, fs};
|
||||
use clap::{Parser, Subcommand, builder::styling::{self}};
|
||||
use crate::{models::vanilla::{VanillaVersionDetail, VanillaVersionType, VanillaVersions}, utils::with_spinner};
|
||||
use clap::{
|
||||
Parser, Subcommand,
|
||||
builder::styling::{self},
|
||||
};
|
||||
use dialoguer::{FuzzySelect, Select, theme::ColorfulTheme};
|
||||
use crate::models::vanilla::{VanillaVersionType, VanillaVersions};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::error::Error;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
fn styles() -> styling::Styles {
|
||||
styling::Styles::styled()
|
||||
|
|
@ -22,17 +29,17 @@ struct Mcsm {
|
|||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
Init {
|
||||
name: String
|
||||
}
|
||||
Init { name: String },
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mcsm = Mcsm::parse();
|
||||
|
||||
|
||||
match mcsm.command {
|
||||
Commands::Init { name } => {
|
||||
fs::create_dir(name)?;
|
||||
Commands::Init { name: project_name } => {
|
||||
tokio::fs::create_dir(&project_name).await?;
|
||||
std::env::set_current_dir(&project_name)?;
|
||||
|
||||
let loaders = vec!["Vanilla", "Fabric"];
|
||||
let loader_index = Select::with_theme(&ColorfulTheme::default())
|
||||
|
|
@ -40,31 +47,74 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||
.items(&loaders)
|
||||
.default(0)
|
||||
.interact()?;
|
||||
|
||||
|
||||
match loader_index {
|
||||
0 => {
|
||||
const URL: &str = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
||||
let res: VanillaVersions = reqwest::get(URL).await?.json().await?;
|
||||
let release_versions = res.versions
|
||||
const URL: &str =
|
||||
"https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
||||
let res = with_spinner("", "", || async {
|
||||
let res: VanillaVersions = reqwest::get(URL).await?.json().await?;
|
||||
Ok(res)
|
||||
}).await?;
|
||||
|
||||
let release_versions = res
|
||||
.versions
|
||||
.iter()
|
||||
.filter(|version| version.version_type == VanillaVersionType::Release)
|
||||
.collect::<Vec<_>>();
|
||||
let release_version_names = release_versions
|
||||
.iter()
|
||||
.map(|version| version.id.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let version_index = FuzzySelect::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Select Version")
|
||||
.items(&release_versions)
|
||||
.items(&release_version_names)
|
||||
.default(0)
|
||||
.interact()?;
|
||||
|
||||
println!("you selected {} with version {}", loaders[loader_index], release_versions[version_index]);
|
||||
|
||||
let version_detail: VanillaVersionDetail =
|
||||
reqwest::get(release_versions[version_index].url.clone())
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
},
|
||||
1 => {
|
||||
println!(
|
||||
"downloading server.jar for {} {}",
|
||||
loaders[loader_index], release_version_names[version_index]
|
||||
);
|
||||
let pb = ProgressBar::new(version_detail.downloads.server.size);
|
||||
pb.set_style(ProgressStyle::default_bar().template(
|
||||
"{bar:40.green/white} {bytes}/{total_bytes} ({bytes_per_sec})",
|
||||
)?);
|
||||
|
||||
let server_bytes = reqwest::get(version_detail.downloads.server.url).await?;
|
||||
let mut file = tokio::fs::File::create("server.jar").await?;
|
||||
let mut stream = server_bytes.bytes_stream();
|
||||
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk?;
|
||||
file.write_all(&chunk).await?;
|
||||
pb.inc(chunk.len() as u64);
|
||||
}
|
||||
pb.finish_with_message("download finished");
|
||||
|
||||
with_spinner("verifying sha1 hash", "sha1 hash verified", || async {
|
||||
let data = tokio::fs::read("server.jar").await?;
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(&data);
|
||||
let hash = hasher.finalize();
|
||||
let hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect();
|
||||
|
||||
if hex != version_detail.downloads.server.sha1 {
|
||||
tokio::fs::remove_file("server.jar").await?;
|
||||
return Err("sha1 verifaction failed for downloaded file".into());
|
||||
}
|
||||
Ok(())
|
||||
}).await?;
|
||||
}
|
||||
_ => return Err("Invalid selection".into())
|
||||
1 => {}
|
||||
_ => return Err("Invalid selection".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use serde::Deserialize;
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct VanillaVersions {
|
||||
pub latest: VanillaLatestVersion,
|
||||
pub latest: VanillaLatestVersion,
|
||||
pub versions: Vec<VanillaVersion>,
|
||||
}
|
||||
|
||||
|
|
@ -29,3 +29,20 @@ pub struct VanillaVersion {
|
|||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct VanillaVersionDetail {
|
||||
pub downloads: VanillaVersionDownloads,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct VanillaVersionDownloads {
|
||||
pub client: VanillaVersionDownloadItem,
|
||||
pub server: VanillaVersionDownloadItem,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct VanillaVersionDownloadItem {
|
||||
pub sha1: String,
|
||||
pub size: u64,
|
||||
pub url: String,
|
||||
}
|
||||
|
|
|
|||
23
src/utils.rs
Normal file
23
src/utils.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use std::{error::Error, future::Future};
|
||||
use indicatif::ProgressBar;
|
||||
|
||||
pub async fn with_spinner<F, Fut, T>(
|
||||
start_msg: &str,
|
||||
finish_msg: &str,
|
||||
f: F,
|
||||
) -> Result<T, Box<dyn Error>>
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
Fut: Future<Output = Result<T, Box<dyn Error>>>,
|
||||
{
|
||||
let pb = ProgressBar::new_spinner();
|
||||
pb.set_message(start_msg.to_string());
|
||||
pb.enable_steady_tick(std::time::Duration::from_millis(100));
|
||||
|
||||
let result = f().await;
|
||||
|
||||
pb.finish_and_clear();
|
||||
println!("{} {}", dialoguer::console::style("✔").green(), finish_msg);
|
||||
|
||||
result
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue