diff --git a/Cargo.lock b/Cargo.lock index 2b29128..a5782bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 048999e..4dad678 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/new/server.jar b/new/server.jar new file mode 100644 index 0000000..eeb02b2 Binary files /dev/null and b/new/server.jar differ diff --git a/src/main.rs b/src/main.rs index a11a238..5f7d7ee 100644 --- a/src/main.rs +++ b/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> { 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> { .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::>(); + let release_version_names = release_versions + .iter() .map(|version| version.id.clone()) .collect::>(); 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()), } } } diff --git a/src/models/vanilla.rs b/src/models/vanilla.rs index c60a791..133a463 100644 --- a/src/models/vanilla.rs +++ b/src/models/vanilla.rs @@ -2,7 +2,7 @@ use serde::Deserialize; #[derive(Deserialize)] pub struct VanillaVersions { - pub latest: VanillaLatestVersion, + pub latest: VanillaLatestVersion, pub versions: Vec, } @@ -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, +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..9e5ba69 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,23 @@ +use std::{error::Error, future::Future}; +use indicatif::ProgressBar; + +pub async fn with_spinner( + start_msg: &str, + finish_msg: &str, + f: F, +) -> Result> +where + F: FnOnce() -> Fut, + Fut: Future>>, +{ + 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 +}