diff --git a/Cargo.lock b/Cargo.lock index e59aa25..1d529ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,17 @@ dependencies = [ "libc", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -618,6 +629,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" name = "mineguard" version = "0.1.0" dependencies = [ + "async-trait", "chrono", "regex", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 7f9141f..3fc9d3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ mc-vanilla = ["dep:serde", "dep:serde_json", "dep:reqwest"] # Add new feature groups here; attach their optional dependencies to the relevant feature list. [dependencies] +async-trait = "0.1.89" chrono = {version = "0.4.42", optional = true} regex = {version = "1.12.2", optional = true} reqwest = { version = "0.12.24", optional = true, features = ["json"] } diff --git a/src/error.rs b/src/error.rs index b861401..c491d00 100644 --- a/src/error.rs +++ b/src/error.rs @@ -107,6 +107,15 @@ pub enum CreationError { #[error("Invalid directory")] DirectoryError, + + #[error("Failed to parse manifest")] + ManifestError, + + #[error("Version does not exist")] + VersionError, + + #[error("Network Error")] + NetworkError, } #[derive(Debug, Clone, Error)] pub enum ManifestError { diff --git a/src/instance/handle.rs b/src/instance/handle.rs index 4430012..2107fe2 100644 --- a/src/instance/handle.rs +++ b/src/instance/handle.rs @@ -41,24 +41,26 @@ pub struct InstanceHandle { impl InstanceHandle { pub fn new_with_params( - root_dir: &str, - jar_path: &str, - mc_version: &str, + root_dir: PathBuf, + jar_path: PathBuf, + mc_version: MinecraftVersion, mc_type: MinecraftType, ) -> Result { - let parsed_version: MinecraftVersion = mc_version - .parse() - .map_err(|_| HandleError::InvalidVersion(mc_version.to_string()))?; + let parsed_version: MinecraftVersion = mc_version; - let root: PathBuf = root_dir.into(); + let root: PathBuf = root_dir.clone().into(); if !root.exists() || !root.is_dir() { - return Err(HandleError::InvalidDirectory(root_dir.to_string())); + return Err(HandleError::InvalidDirectory( + root_dir.to_str().unwrap().to_string(), + )); } - let path: PathBuf = jar_path.into(); + let path: PathBuf = jar_path.clone().into(); let conc = root.join(path.clone()); if !path.is_relative() || !conc.is_file() { - return Err(HandleError::InvalidPathJAR(jar_path.to_string())); + return Err(HandleError::InvalidPathJAR( + jar_path.to_str().unwrap().to_string(), + )); } let data = InstanceData { diff --git a/src/manifests/vanilla.rs b/src/manifests/vanilla.rs index 1bbea80..2eb394d 100644 --- a/src/manifests/vanilla.rs +++ b/src/manifests/vanilla.rs @@ -1,5 +1,6 @@ #![cfg(feature = "mc-vanilla")] +use async_trait::async_trait; use reqwest::Client; use serde::Deserialize; @@ -12,13 +13,13 @@ pub struct VanillaManifestV2 { } #[derive(Debug, Clone, Deserialize)] -struct VanillaManifestV2Latest { +pub struct VanillaManifestV2Latest { release: String, snapshot: String, } #[derive(Debug, Clone, Deserialize)] -struct VanillaManifestV2Version { +pub struct VanillaManifestV2Version { id: String, #[serde(rename = "type")] mc_type: String, @@ -28,7 +29,52 @@ struct VanillaManifestV2Version { release_time: String, sha1: String, #[serde(rename = "complianceLevel")] - compliance_level: String, + compliance_level: u32, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct VanillaReleaseManifest { + downloads: VanillaReleaseManifestDownloads, +} +#[derive(Debug, Clone, Deserialize)] +pub struct VanillaReleaseManifestDownloads { + client: VanillaReleaseManifestDownloadsItem, + client_mappings: VanillaReleaseManifestDownloadsItem, + server: VanillaReleaseManifestDownloadsItem, + server_mappings: VanillaReleaseManifestDownloadsItem, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct VanillaReleaseManifestDownloadsItem { + sha1: String, + size: u32, + url: String, +} + +impl VanillaReleaseManifest { + pub async fn load(version: VanillaManifestV2Version) -> Result { + let client = Client::new(); + + let manifest: VanillaReleaseManifest = client + .get(&version.url) + .send() + .await + .map_err(|_| ManifestError::LoadUrlError)? + .error_for_status() + .map_err(|_| ManifestError::LoadUrlError)? + .json() + .await + .map_err(|e| { + eprintln!("{}", e); + ManifestError::JsonParseError + })?; + + Ok(manifest) + } + + pub fn server_url(&self) -> String { + self.downloads.server.url.clone() + } } impl VanillaManifestV2 { @@ -44,7 +90,10 @@ impl VanillaManifestV2 { .map_err(|_| ManifestError::LoadUrlError)? .json() .await - .map_err(|_| ManifestError::JsonParseError)?; + .map_err(|e| { + eprintln!("{}", e); + ManifestError::JsonParseError + })?; Ok(manifest) } diff --git a/src/server/domain.rs b/src/server/domain.rs index d994fcd..f0d9e8b 100644 --- a/src/server/domain.rs +++ b/src/server/domain.rs @@ -1,12 +1,18 @@ -use std::{path::PathBuf, str::FromStr}; +use std::{ops::RangeInclusive, path::PathBuf, str::FromStr}; -use tokio::sync::RwLock; +use tokio::{ + fs::{File, create_dir}, + io::{self, AsyncWriteExt}, + sync::RwLock, +}; use uuid::Uuid; use crate::{ config::{MinecraftType, MinecraftVersion, Version}, error::CreationError, instance::InstanceHandle, + manifests::vanilla::{VanillaManifestV2, VanillaManifestV2Version, VanillaReleaseManifest}, + server, }; pub struct MineGuardConfig { @@ -18,8 +24,8 @@ pub struct MineGuardConfig { } pub struct MineGuardServer { - handle: RwLock, - config: RwLock, + pub handle: RwLock, + pub config: RwLock, } impl MineGuardConfig { @@ -35,7 +41,7 @@ impl MineGuardConfig { } impl MineGuardServer { - pub fn create( + pub async fn create( mc_version: MinecraftVersion, mc_type: MinecraftType, directory: PathBuf, @@ -44,6 +50,74 @@ impl MineGuardServer { return Err(CreationError::DirectoryError); } - todo!() + let uuid = Uuid::new_v4(); + + let server_root = directory.join(uuid.to_string()); + let jar_path_rel = + PathBuf::from_str("server.jar").map_err(|_| CreationError::DirectoryError)?; + let jar_path_full = server_root.join(jar_path_rel.clone()); + + create_dir(server_root.clone()) + .await + .map_err(|_| CreationError::DirectoryError)?; + + let mut url = String::new(); + + if mc_type == MinecraftType::Vanilla { + let vanilla_manifest = VanillaManifestV2::load() + .await + .map_err(|_| CreationError::ManifestError)?; + + let find_ver = match vanilla_manifest + .find(mc_version.clone()) + .map_err(|_| CreationError::ManifestError)? + { + Some(val) => val, + None => return Err(CreationError::VersionError), + }; + + let release_manifest = VanillaReleaseManifest::load(find_ver) + .await + .map_err(|_| CreationError::ManifestError)?; + + url = release_manifest.server_url(); + } + + let resp = reqwest::get(url) + .await + .map_err(|_| CreationError::NetworkError)?; + let mut body = resp + .bytes() + .await + .map_err(|_| CreationError::NetworkError)?; + let mut out = File::create(jar_path_full) + .await + .map_err(|_| CreationError::DirectoryError)?; + out.write_all_buf(&mut body) + .await + .map_err(|_| CreationError::DirectoryError)?; + + let config = MineGuardConfig { + uuid: uuid, + server_dir: server_root, + jar_path: jar_path_rel, + mc_version: mc_version, + mc_type: mc_type, + }; + + let handle = InstanceHandle::new_with_params( + config.server_dir.clone(), + config.jar_path.clone(), + config.mc_version.clone(), + config.mc_type.clone(), + ) + .map_err(|_| CreationError::CreationError)?; + + let server = MineGuardServer { + config: RwLock::new(config), + handle: RwLock::new(handle), + }; + + Ok(server) } }