diff --git a/src/api/app/actions/build/server_jar.rs b/src/api/app/actions/build/server_jar.rs index bc0900d..ccfb0a4 100644 --- a/src/api/app/actions/build/server_jar.rs +++ b/src/api/app/actions/build/server_jar.rs @@ -5,13 +5,13 @@ use anyhow::Result; use crate::api::{app::App, models::Environment}; impl App { - /// Installs the server jar according to [`crate::api::models::server::Server::jar`] + /// Installs the server jar according to [`crate::api::models::server::Server::get_jar`] pub async fn action_install_jar(&self, base: &Path) -> Result<()> { - if let Some(jar) = self.server.read().await.as_ref().map(|(_, server)| { - server.jar.clone() - }).flatten() { + if let Some((_, server)) = &*self.server.read().await { println!("Installing server jar"); + let jar = server.get_jar(&self).await?; + let steps = jar.resolve_steps(&self, Environment::Server).await?; self.execute_steps(base, &steps).await?; diff --git a/src/api/app/actions/init/mod.rs b/src/api/app/actions/init/mod.rs index d1ff2cf..0a96c5d 100644 --- a/src/api/app/actions/init/mod.rs +++ b/src/api/app/actions/init/mod.rs @@ -8,50 +8,5 @@ use crate::api::{ }; impl App { - pub async fn action_init_server(&self) -> Result<()> { - cliclack::intro("Creating a new server...")?; - - let path = PathBuf::from(".").join(SERVER_TOML); - - if let Some((_, server)) = &*self.server.read().await { - let con = cliclack::confirm(format!("Server with name '{}' found! Continue?", server.name)) - .initial_value(false) - .interact()?; - - if con { - cliclack::note("Warning", "Current server might get overwritten")?; - } else { - cliclack::outro_cancel("Cancelled")?; - return Ok(()); - } - } - - let name: String = cliclack::input("Name of the server?").interact()?; - - let mut server = Server { - name, - ..Default::default() - }; - - { - let mut wg = self.server.write().await; - *wg = Some((path, server)); - } - - cliclack::outro("Saved!")?; - - Ok(()) - } - - pub async fn action_init_network(&self) -> Result<()> { - cliclack::intro("initializing network")?; - - let name: String = cliclack::input("Name of the network?").interact()?; - - let mut nw = Network { name, ..Default::default() }; - - //self.network = Some(Arc::new(RwLock::new(nw))); - - Ok(()) - } + } diff --git a/src/api/app/actions/script/mod.rs b/src/api/app/actions/script/mod.rs index 71928ad..70477be 100644 --- a/src/api/app/actions/script/mod.rs +++ b/src/api/app/actions/script/mod.rs @@ -18,11 +18,7 @@ impl App { let mut args = vec![]; - let java = if let Some(v) = &server.launcher.java_version { - get_java_installation_for(*v).await.map(|j| j.path.to_string_lossy().into_owned()).unwrap_or(String::from("java")) - } else { - String::from("java") - }; + let java = server.get_java().await; args.push(java); args.extend(server.get_arguments()); diff --git a/src/api/app/collect.rs b/src/api/app/collect.rs index c6f707a..d049e92 100644 --- a/src/api/app/collect.rs +++ b/src/api/app/collect.rs @@ -11,7 +11,10 @@ impl App { let mut sources = vec![]; if let Some((server_path, server)) = &*self.server.read().await { + let server_path = server_path.parent().unwrap().to_path_buf(); if let Some((network_path, network)) = &*self.network.read().await { + let network_path = network_path.parent().unwrap().to_path_buf(); + if let Some(group) = network.groups.get("global") { for source in &group.sources { sources.push((network_path.clone(), source.clone())); diff --git a/src/api/app/step/execute_java.rs b/src/api/app/step/execute_java.rs index 975d093..38a219a 100644 --- a/src/api/app/step/execute_java.rs +++ b/src/api/app/step/execute_java.rs @@ -20,9 +20,7 @@ impl App { .find(|j| j.version >= version.unwrap_or_default()) .ok_or(anyhow!("Java with version {} or higher not found, cannot proceed", version.map(|v| v.to_string()).unwrap_or("any".to_owned())))?; - let args = args.iter().map(String::as_str).collect::>(); - - let mut proc = JavaProcess::new(&dir.canonicalize()?, &java.path, &args)?; + let mut proc = JavaProcess::new(&dir.canonicalize()?, &java.path, args)?; fn on_line(line: &str) { println!("| {line}"); diff --git a/src/api/models/launcher/preset_flags.rs b/src/api/models/launcher/preset_flags.rs index ffd1087..d0ee928 100644 --- a/src/api/models/launcher/preset_flags.rs +++ b/src/api/models/launcher/preset_flags.rs @@ -18,6 +18,7 @@ impl PresetFlags { Self::None => "", } .split(char::is_whitespace) + .filter(|x| !x.is_empty()) .map(ToOwned::to_owned) .collect() } diff --git a/src/api/models/mrpack/mod.rs b/src/api/models/mrpack/mod.rs index de57089..99be41a 100644 --- a/src/api/models/mrpack/mod.rs +++ b/src/api/models/mrpack/mod.rs @@ -10,7 +10,14 @@ pub const MRPACK_INDEX_FILE: &str = "modrinth.index.json"; use crate::api::{app::App, utils::accessor::Accessor}; -use super::{Addon, AddonTarget, AddonType, Environment}; +use super::{server::ServerJar, Addon, AddonTarget, AddonType}; + + +pub async fn resolve_mrpack_serverjar(app: &App, mut accessor: Accessor) -> Result { + let index: MRPackIndex = accessor.json(app, MRPACK_INDEX_FILE).await?; + + Ok(ServerJar::try_from(index.dependencies.clone())?) +} pub async fn resolve_mrpack_addons(app: &App, mut accessor: Accessor) -> Result> { let mut addons = vec![]; diff --git a/src/api/models/packwiz/mod.rs b/src/api/models/packwiz/mod.rs index ebda1f8..5df3c65 100644 --- a/src/api/models/packwiz/mod.rs +++ b/src/api/models/packwiz/mod.rs @@ -1,17 +1,23 @@ mod packwiz_mod; mod packwiz_pack; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use anyhow::{bail, Result}; pub use packwiz_mod::*; pub use packwiz_pack::*; -use crate::api::{app::App, models::AddonType, step::Step, utils::{accessor::Accessor, url::get_filename_from_url}}; +use crate::api::{app::App, models::AddonType, step::Step, utils::accessor::Accessor}; pub static PACK_TOML: &str = "pack.toml"; -use super::{Addon, AddonTarget}; +use super::{server::ServerJar, Addon, AddonTarget}; + +pub async fn resolve_packwiz_serverjar(app: &App, mut accessor: Accessor) -> Result { + let pack: PackwizPack = accessor.toml(app, PACK_TOML).await?; + + Ok(ServerJar::try_from(pack.versions.clone())?) +} pub async fn resolve_packwiz_addons(app: &App, mut accessor: Accessor) -> Result> { let mut addons = vec![]; diff --git a/src/api/models/server/mod.rs b/src/api/models/server/mod.rs index c2a3993..e90733b 100644 --- a/src/api/models/server/mod.rs +++ b/src/api/models/server/mod.rs @@ -1,9 +1,12 @@ use std::collections::HashMap; +use anyhow::{anyhow, Result}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use super::{launcher::ServerLauncher, markdown::MarkdownOptions, Source}; +use crate::api::{app::App, tools::java::get_java_installation_for}; + +use super::{launcher::ServerLauncher, markdown::MarkdownOptions, mrpack::resolve_mrpack_serverjar, packwiz::resolve_packwiz_serverjar, ModpackType, Source, SourceType}; mod server_flavor; mod server_type; @@ -55,6 +58,33 @@ impl Default for Server { } impl Server { + /// Gets the ServerJar via `jar` OR `Source` where `type=modpack` + pub async fn get_jar(&self, app: &App) -> Result { + let relative_to = app.server.read().await.as_ref().map(|(p, _)| p.clone()); + + if let Some(jar) = &self.jar { + Ok(jar.clone()) + } else { + let source = self.sources.iter().find(|s| matches!(s.source_type, SourceType::Modpack { .. })) + .ok_or(anyhow!("Can't find a ServerJar type because no [jar] OR Source with type=modpack defined"))?; + + let accessor = source.accessor(&relative_to.ok_or(anyhow!("relative_to error"))?)?; + match source.modpack_type().unwrap() { + ModpackType::MRPack => resolve_mrpack_serverjar(app, accessor).await, + ModpackType::Packwiz => resolve_packwiz_serverjar(app, accessor).await, + ModpackType::Unsup => todo!() + } + } + } + + pub async fn get_java(&self) -> String { + if let Some(v) = self.launcher.java_version { + get_java_installation_for(v).await.map(|j| j.path.to_string_lossy().into_owned()).unwrap_or(String::from("java")) + } else { + String::from("java") + } + } + pub fn get_execution_arguments(&self) -> Vec { self.jar.as_ref().map(|s| s.get_execution_arguments()).unwrap_or_default() } @@ -86,6 +116,6 @@ impl Server { args.extend(self.launcher.game_args.split_whitespace().map(ToOwned::to_owned)); - args + args.into_iter().filter(|x| !x.is_empty()).collect() } } diff --git a/src/api/models/server/server_type.rs b/src/api/models/server/server_type.rs index 481323f..94b8b8b 100644 --- a/src/api/models/server/server_type.rs +++ b/src/api/models/server/server_type.rs @@ -1,7 +1,9 @@ +use std::collections::HashMap; + use crate::api::{ app::App, models::{Addon, AddonTarget, AddonType, Environment}, sources::buildtools, step::Step, utils::serde::* }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -36,6 +38,43 @@ pub struct ServerJar { pub server_type: ServerType, } +impl TryFrom> for ServerJar { + type Error = anyhow::Error; + + fn try_from(value: HashMap) -> std::result::Result { + let mut server_type = None; + + if let Some(v) = value.get("fabric").cloned() { + server_type = Some(ServerType::Fabric { loader: v, installer: String::from("latest") }) + } + + if let Some(v) = value.get("fabric-loader").cloned() { + server_type = Some(ServerType::Fabric { loader: v, installer: String::from("latest") }) + } + + if let Some(v) = value.get("quilt").cloned() { + server_type = Some(ServerType::Quilt { loader: v, installer: String::from("latest") }) + } + + if let Some(v) = value.get("quilt-loader").cloned() { + server_type = Some(ServerType::Quilt { loader: v, installer: String::from("latest") }) + } + + if let Some(v) = value.get("forge").cloned() { + server_type = Some(ServerType::Forge { loader: v }) + } + + if let Some(v) = value.get("neoforge").cloned() { + server_type = Some(ServerType::NeoForge { loader: v }) + } + + Ok(ServerJar { + mc_version: value.get("minecraft").ok_or(anyhow!("Can't resolve minecraft version"))?.clone(), + server_type: server_type.ok_or(anyhow!("Can't resolve a ServerType"))?, + }) + } +} + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, JsonSchema)] #[serde(tag = "type", rename_all = "lowercase")] pub enum ServerType { diff --git a/src/api/models/source.rs b/src/api/models/source.rs index 6a42e5f..a99c587 100644 --- a/src/api/models/source.rs +++ b/src/api/models/source.rs @@ -57,7 +57,9 @@ impl Source { pub async fn resolve_addons(&self, app: &App, relative_to: &Path) -> Result> { match &self.source_type { SourceType::File { path } => { - let file: AddonListFile = read_toml(&with_extension_if_none(&relative_to.join(path), "toml")).with_context(|| format!("Source: File => {path}"))?; + let path = with_extension_if_none(&relative_to.join(path), "toml"); + log::info!("Source: File => {path:?}"); + let file: AddonListFile = read_toml(&path).with_context(|| format!("Source: File => {path:?}"))?; Ok(file.flatten()) }, diff --git a/src/api/sources/papermc/mod.rs b/src/api/sources/papermc/mod.rs index 3652832..0062f8a 100644 --- a/src/api/sources/papermc/mod.rs +++ b/src/api/sources/papermc/mod.rs @@ -75,7 +75,7 @@ impl<'a> PaperMCAPI<'a> { let metadata = FileMeta { cache: Some(CacheLocation(CACHE_DIR.into(), format!("{project}/{}", download.name))), - filename: download.name.clone(), + filename: String::from("server.jar"), ..Default::default() }; diff --git a/src/api/tools/java/check.rs b/src/api/tools/java/check.rs index 0d66e67..b570936 100644 --- a/src/api/tools/java/check.rs +++ b/src/api/tools/java/check.rs @@ -42,7 +42,7 @@ pub fn check_java(path: &Path) -> Option { Some(JavaInstallation { path, - version: JavaInstallation::get_version_from(info.get("java.version")?).ok()?, + version: JavaInstallation::parse_version(info.get("java.version")?).ok()?, architecture: info.get("os.arch")?.clone(), vendor: info.get("java.vendor")?.clone(), }) diff --git a/src/api/tools/java/installation.rs b/src/api/tools/java/installation.rs index 1adfa8b..ab7bba6 100644 --- a/src/api/tools/java/installation.rs +++ b/src/api/tools/java/installation.rs @@ -16,7 +16,7 @@ pub struct JavaInstallation { } impl JavaInstallation { - pub fn get_version_from(version: &str) -> Result { + pub fn parse_version(version: &str) -> Result { let mut split = version.split('.'); let str = match (split.next(), split.next()) { diff --git a/src/api/tools/java/mod.rs b/src/api/tools/java/mod.rs index 3e14584..511fbd5 100644 --- a/src/api/tools/java/mod.rs +++ b/src/api/tools/java/mod.rs @@ -4,9 +4,9 @@ pub type JavaVersion = u32; mod installation; mod find; mod check; -use std::{path::Path, process::{ExitStatus, Stdio}}; +use std::{ffi::OsStr, fmt::Debug, path::Path, process::{ExitStatus, Stdio}}; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; pub use installation::*; pub use check::*; use tokio::{io::{AsyncBufReadExt, BufReader}, process::{Child, Command}}; @@ -18,23 +18,34 @@ pub struct JavaProcess { } impl JavaProcess { - pub fn new( + pub fn new + Debug, S1: AsRef + Debug, S2: AsRef + Debug>( dir: &Path, - java: &Path, - args: &[&str], + java: S2, + args: I, ) -> Result { // JRE is stupid - let dir = std::env::current_dir()? - .diff_to(dir) - .ok_or(anyhow!("Couldn't diff paths"))?; + let dir = if std::env::consts::OS == "windows" { + std::env::current_dir()? + .try_diff_to(dir)? + } else { + dir.to_path_buf() + }; - let child = Command::new(java) + log::info!("Running java process"); + log::info!("Cwd: {dir:?}"); + log::info!("Java binary: {java:#?}"); + log::info!("Arguments: {args:#?}"); + + let child = Command::new(&java) .args(args) .current_dir(dir) .stderr(Stdio::inherit()) .stdout(Stdio::piped()) .stdin(Stdio::piped()) - .spawn()?; + .spawn() + .with_context(|| format!("Spawning java process"))?; + + log::info!("Child process spawned"); Ok(Self { child, diff --git a/src/api/ws/mod.rs b/src/api/ws/mod.rs index 03a9466..9e88689 100644 --- a/src/api/ws/mod.rs +++ b/src/api/ws/mod.rs @@ -16,12 +16,12 @@ pub type WebsocketSink = SplitSink, Message>; pub type WebsocketStream = SplitStream>; pub struct WebsocketServer { - app: App, + app: Arc, clients: RwLock>, } impl WebsocketServer { - pub fn new(app: App) -> Arc { + pub fn new(app: Arc) -> Arc { Arc::new(Self { app, clients: RwLock::new(vec![]), diff --git a/src/commands/build.rs b/src/commands/build.rs index e209705..4c1e0ac 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -1,26 +1,35 @@ use std::{path::{Path, PathBuf}, sync::Arc}; -use anyhow::Result; +use anyhow::{anyhow, Result}; use crate::api::app::App; #[derive(clap::Args)] -pub struct Args { +pub struct BuildArgs { #[arg(long, default_value = "output/server")] pub output: PathBuf, } -pub async fn run(app: Arc, args: Args) -> Result<()> { - let Some(server_path) = app.server.read().await.as_ref().map(|(path, _)| path.parent().unwrap().to_owned()) else { - log::error!("No `server.toml` found to build server"); - return Ok(()) - }; +impl BuildArgs { + pub async fn get_base_dir(&self, app: &App) -> Result { + Ok(app.server + .read() + .await + .as_ref() + .ok_or(anyhow!("No `server.toml` found"))? + .0.parent().unwrap().to_owned() + .join(&self.output)) + } +} - let base = server_path.join(&args.output); +pub async fn run(app: Arc, args: BuildArgs) -> Result<()> { + let base = args.get_base_dir(&app).await?; log::info!("Build output: {base:?}"); app.action_build(&base).await?; + log::info!("Build complete"); + Ok(()) } diff --git a/src/commands/init.rs b/src/commands/init.rs index ac88f9f..883cf17 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,19 +1,87 @@ -use std::{path::Path, sync::Arc}; +use std::{path::{Path, PathBuf}, sync::Arc}; use anyhow::Result; -use crate::api::app::App; +use crate::api::{app::App, models::{network::{Network, NETWORK_TOML}, server::{Server, SERVER_TOML}}, utils::toml::write_toml}; #[derive(clap::Args)] pub struct Args { /// The name of the server #[arg(long)] name: Option, + + /// Folder to initialize in + #[arg(long, default_value = ".")] + dir: String, } pub async fn run(app: Arc, args: Args) -> Result<()> { - app.action_init_server() - .await?; + init_server(app, args).await?; + + Ok(()) +} + +pub async fn init_server(app: Arc, args: Args) -> Result<()> { + cliclack::intro("Creating a new server...")?; + + let dir = PathBuf::from(args.dir); + let path = dir.join(SERVER_TOML); + + if let Some((_, server)) = &*app.server.read().await { + let con = cliclack::confirm(format!( + "Server with name '{}' found! Continue?", + server.name + )) + .initial_value(false) + .interact()?; + + if con { + cliclack::note("Warning", "Current server might get overwritten")?; + } else { + cliclack::outro_cancel("Cancelled")?; + return Ok(()); + } + } + + let name: String = args.name.map(Ok).unwrap_or_else(|| cliclack::input("Name of the server?").interact())?; + + let mut server = Server { + name, + ..Default::default() + }; + + { + write_toml(&dir, SERVER_TOML, &server)?; + + let mut wg = app.server.write().await; + *wg = Some((path, server)); + } + + cliclack::outro("Saved!")?; + + Ok(()) +} + +pub async fn action_init_network(app: Arc, args: Args) -> Result<()> { + cliclack::intro("initializing network")?; + + let dir = PathBuf::from(args.dir); + let path = dir.join(NETWORK_TOML); + + let name: String = args.name.map(Ok).unwrap_or_else(|| cliclack::input("Name of the network?").interact())?; + + let mut nw = Network { + name, ..Default::default() + }; + + { + write_toml(&dir, NETWORK_TOML, &nw)?; + + let mut wg = app.network.write().await; + *wg = Some((path, nw)); + } + + cliclack::outro("Saved!")?; Ok(()) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index baf80a8..26649e6 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -4,3 +4,5 @@ pub mod sources; pub mod java; pub mod markdown; pub mod migrate; +pub mod websocket; +pub mod run; diff --git a/src/commands/run.rs b/src/commands/run.rs new file mode 100644 index 0000000..192a25f --- /dev/null +++ b/src/commands/run.rs @@ -0,0 +1,38 @@ +use std::{path::PathBuf, sync::Arc}; + +use anyhow::{Context, Result}; + +use crate::api::{app::App, tools::java::{get_java_installation_for, JavaProcess}, ws::WebsocketServer}; + +#[derive(clap::Args)] +pub struct RunArgs { + #[command(flatten)] + pub build_args: super::build::BuildArgs, +} + +pub async fn run(app: Arc, args: RunArgs) -> Result<()> { + let base = args.build_args.get_base_dir(&app).await?; + + super::build::run(app.clone(), args.build_args).await?; + + let rg = app.server.read().await; + let (_, server) = rg.as_ref().unwrap(); + let java = server.get_java().await; + let args = server.get_arguments(); + drop(rg); + + log::info!("Starting process..."); + + let mut process = JavaProcess::new(&base, java, args)?; + + process.lines(|line| { + println!("| {line}"); + }); + + let e = process.wait().await?; + + println!("{e:#?}"); + + Ok(()) +} + diff --git a/src/commands/websocket.rs b/src/commands/websocket.rs new file mode 100644 index 0000000..a78e06a --- /dev/null +++ b/src/commands/websocket.rs @@ -0,0 +1,19 @@ +use std::sync::Arc; + +use anyhow::{Context, Result}; + +use crate::api::{app::App, ws::WebsocketServer}; + +#[derive(clap::Args)] +pub struct Args { + #[arg(long, default_value = "0.0.0.0:6969")] + addr: String, +} + +pub async fn run(app: Arc, args: Args) -> Result<()> { + let ws = WebsocketServer::new(app); + + ws.start(&args.addr).await.context("Running WebSocket Server")?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index daa40e1..f412cb0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,8 @@ enum Commands { Init(commands::init::Args), #[command(subcommand)] Sources(commands::sources::Commands), - Build(commands::build::Args), + Build(commands::build::BuildArgs), + Run(commands::run::RunArgs), #[command(subcommand)] Java(commands::java::Commands), #[command(alias = "md", subcommand)] @@ -41,8 +42,10 @@ async fn main() -> Result<()> { Commands::Init(args) => commands::init::run(app, args).await, Commands::Sources(args) => commands::sources::run(app, args).await, Commands::Build(args) => commands::build::run(app, args).await, + Commands::Run(args) => commands::run::run(app, args).await, Commands::Java(args) => commands::java::run(app, args).await, Commands::Markdown(args) => commands::markdown::run(app, args).await, Commands::Migrate(args) => commands::migrate::run(app, args).await, + } }