Initial rewrite start
This commit is contained in:
1425
Cargo.lock
generated
1425
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
42
Cargo.toml
42
Cargo.toml
@@ -1,36 +1,26 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mineguard"
|
name = "mgrewrite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Opinionated Minecraft server supervisor engine built primarily for RustyMine"
|
|
||||||
readme = "README.md"
|
|
||||||
homepage = "https://mineguard.h3cx.dev"
|
|
||||||
repository = "https://github.com/H3ct0r55/MineGuard"
|
|
||||||
license = "MIT"
|
|
||||||
# publish set to false during development
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["core", "events", "mc-vanilla"]
|
default = ["core", "mc-vanilla"]
|
||||||
# Core runtime requirements for the currently implemented functionality.
|
|
||||||
core = ["dep:thiserror", "dep:tokio", "dep:tokio-stream", "dep:tokio-util"]
|
|
||||||
# Placeholder for upcoming event-driven functionality.
|
|
||||||
events = ["dep:uuid", "dep:chrono", "dep:regex"]
|
|
||||||
|
|
||||||
mc-vanilla = ["dep:serde", "dep:serde_json", "dep:reqwest"]
|
core = ["dep:serde", "dep:serde_json", "dep:uuid", "dep:tokio", "dep:tokio-util"]
|
||||||
# Add new feature groups here; attach their optional dependencies to the relevant feature list.
|
|
||||||
|
version-custom = []
|
||||||
|
|
||||||
|
events = []
|
||||||
|
|
||||||
|
mc-vanilla = []
|
||||||
|
|
||||||
|
mc-paper = []
|
||||||
|
tokio = ["dep:tokio"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = "0.1.89"
|
serde = { version = "1.0.228", features = ["derive"], optional = true }
|
||||||
chrono = {version = "0.4.42", optional = true}
|
|
||||||
regex = {version = "1.12.2", optional = true}
|
|
||||||
reqwest = { version = "0.12.24", optional = true, features = ["json"] }
|
|
||||||
serde = { version = "1.0.228", optional = true, features = ["derive"] }
|
|
||||||
serde_json = {version = "1.0.145", optional = true}
|
serde_json = {version = "1.0.145", optional = true}
|
||||||
thiserror = { version = "2.0.17", optional = true }
|
thiserror = "2.0.17"
|
||||||
# Core async runtime and utilities
|
tokio = { version = "1.48.0", features = ["fs", "io-std", "io-util", "process", "rt-multi-thread"], optional = true }
|
||||||
# Add new feature-specific optional dependencies alongside the relevant feature entry above.
|
tokio-util ={version = "0.7.17", optional = true}
|
||||||
tokio = { version = "1.48.0", features = ["process", "rt-multi-thread", "macros", "io-std", "io-util"], optional = true }
|
|
||||||
tokio-stream = { version = "0.1.17", features = ["full", "io-util", "signal", "tokio-util"], optional = true }
|
|
||||||
tokio-util = { version = "0.7.17", features = ["full"], optional = true }
|
|
||||||
uuid = { version = "1.19.0", features = ["serde", "v4"], optional = true }
|
uuid = { version = "1.19.0", features = ["serde", "v4"], optional = true }
|
||||||
|
|||||||
26
src/config/config.rs
Normal file
26
src/config/config.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::config::version::{MinecraftType, MinecraftVersion};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub core_path: PathBuf,
|
||||||
|
pub jar_path: PathBuf,
|
||||||
|
pub mc_version: MinecraftVersion,
|
||||||
|
pub mc_type: MinecraftType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerConfig {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: Uuid::new_v4(),
|
||||||
|
core_path: PathBuf::new(),
|
||||||
|
jar_path: PathBuf::new(),
|
||||||
|
mc_version: MinecraftVersion::Unknown,
|
||||||
|
mc_type: MinecraftType::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,2 @@
|
|||||||
pub mod stream;
|
pub mod config;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
|
||||||
pub use stream::{LogMeta, StreamLine, StreamSource};
|
|
||||||
pub use version::{MinecraftType, MinecraftVersion, Snapshot, Version};
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
use std::fmt::{self, Display};
|
|
||||||
|
|
||||||
use uuid::{Uuid, timestamp};
|
|
||||||
|
|
||||||
use crate::instance::InstanceStatus;
|
|
||||||
|
|
||||||
use super::line::StreamLine;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum EventPayload {
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
StateChange {
|
|
||||||
old: InstanceStatus,
|
|
||||||
new: InstanceStatus,
|
|
||||||
},
|
|
||||||
|
|
||||||
StdLine {
|
|
||||||
line: StreamLine,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct InstanceEvent {
|
|
||||||
pub id: Uuid,
|
|
||||||
|
|
||||||
pub timestamp: chrono::DateTime<chrono::Utc>,
|
|
||||||
|
|
||||||
pub payload: EventPayload,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum InternalEvent {
|
|
||||||
ServerStarted,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InstanceEvent {
|
|
||||||
pub fn stdout<S: Into<String>>(line: S) -> Self {
|
|
||||||
let line = line.into();
|
|
||||||
let s_line = StreamLine::stdout(line);
|
|
||||||
let timestamp = s_line.extract_timestamp().unwrap_or(chrono::Utc::now());
|
|
||||||
let payload = EventPayload::StdLine { line: s_line };
|
|
||||||
|
|
||||||
Self {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
timestamp,
|
|
||||||
payload,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stderr<S: Into<String>>(line: S) -> Self {
|
|
||||||
let line = line.into();
|
|
||||||
let s_line = StreamLine::stderr(line);
|
|
||||||
let timestamp = s_line.extract_timestamp().unwrap_or(chrono::Utc::now());
|
|
||||||
let payload = EventPayload::StdLine { line: s_line };
|
|
||||||
|
|
||||||
Self {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
timestamp,
|
|
||||||
payload,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(payload: EventPayload) -> Self {
|
|
||||||
let timestamp = chrono::Utc::now();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
timestamp,
|
|
||||||
payload,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for InstanceEvent {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let head = format!(
|
|
||||||
"UUID: {}\nTimestamp:{}\nPayload:\n",
|
|
||||||
self.id, self.timestamp
|
|
||||||
);
|
|
||||||
match self.payload.clone() {
|
|
||||||
EventPayload::StdLine { line } => {
|
|
||||||
let full = format!("{}{}", head, line);
|
|
||||||
writeln!(f, "{}", full)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
EventPayload::StateChange { old, new } => {
|
|
||||||
let full = format!("{}State changed: {:?} -> {:?}", head, old, new);
|
|
||||||
writeln!(f, "{}", full)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
use std::fmt::{self, Display};
|
|
||||||
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
use chrono::{DateTime, Local, NaiveTime, TimeZone, Utc};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum StreamSource {
|
|
||||||
Stdout,
|
|
||||||
Stderr,
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
Event,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct StreamLine {
|
|
||||||
pub line: String,
|
|
||||||
pub source: StreamSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamLine {
|
|
||||||
pub fn new<S: Into<String>>(line: S, source: StreamSource) -> Self {
|
|
||||||
let line = line.into();
|
|
||||||
let re = Regex::new(r#"^\[[^\]]*\]\s*\[[^\]]*\]:\s*"#).unwrap();
|
|
||||||
let line = re.replace(&line, "").to_string();
|
|
||||||
Self { line, source }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stdout<S: Into<String>>(line: S) -> Self {
|
|
||||||
let line = line.into();
|
|
||||||
Self {
|
|
||||||
line,
|
|
||||||
source: StreamSource::Stdout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stderr<S: Into<String>>(line: S) -> Self {
|
|
||||||
let line = line.into();
|
|
||||||
Self {
|
|
||||||
line,
|
|
||||||
source: StreamSource::Stderr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn msg(&self) -> String {
|
|
||||||
self.line.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_timestamp(&self) -> Option<DateTime<Utc>> {
|
|
||||||
let input = self.line.as_str();
|
|
||||||
let re = Regex::new(r"\[(.*?)\]").unwrap();
|
|
||||||
let time_s = re.captures(input).map(|v| v[1].to_string());
|
|
||||||
time_s.as_ref()?;
|
|
||||||
let time = NaiveTime::parse_from_str(&time_s.unwrap(), "%H:%M:%S").ok()?;
|
|
||||||
|
|
||||||
let today = Local::now().date_naive();
|
|
||||||
let naive_dt = today.and_time(time);
|
|
||||||
|
|
||||||
let local_dt = Local.from_local_datetime(&naive_dt).unwrap();
|
|
||||||
|
|
||||||
let utc_dt = local_dt.with_timezone(&Utc);
|
|
||||||
|
|
||||||
Some(utc_dt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for StreamLine {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
use std::fmt::{self, Display};
|
|
||||||
|
|
||||||
use crate::error::ParserError;
|
|
||||||
|
|
||||||
#[cfg(feature = "mc-vanilla")]
|
|
||||||
pub struct LogMeta {
|
|
||||||
pub time: String,
|
|
||||||
pub thread: String,
|
|
||||||
pub level: LogLevel,
|
|
||||||
pub msg: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mc-vanilla")]
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub enum LogLevel {
|
|
||||||
Info,
|
|
||||||
Warn,
|
|
||||||
Error,
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mc-vanilla")]
|
|
||||||
impl LogMeta {
|
|
||||||
pub fn new<S: Into<String>>(line: S) -> Result<Option<Self>, ParserError> {
|
|
||||||
let line: String = line.into();
|
|
||||||
let line = line.trim();
|
|
||||||
|
|
||||||
if !line.starts_with('[') {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let time_end = match line.find(']') {
|
|
||||||
Some(i) => i,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
let time = line[1..time_end].to_string();
|
|
||||||
|
|
||||||
let meta_start = match line[time_end + 1..].find('[') {
|
|
||||||
Some(j) => time_end + 1 + j,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let msg_sep = match line[meta_start..].find("]: ") {
|
|
||||||
Some(k) => meta_start + k,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let meta = &line[(meta_start + 1)..msg_sep]; // inside the brackets
|
|
||||||
let msg = line[(msg_sep + 3)..].to_string(); // after "]: "
|
|
||||||
|
|
||||||
let mut thread_level = meta.splitn(2, '/');
|
|
||||||
let thread = thread_level
|
|
||||||
.next()
|
|
||||||
.ok_or(ParserError::ParserError)?
|
|
||||||
.to_string();
|
|
||||||
let level_str = thread_level
|
|
||||||
.next()
|
|
||||||
.ok_or(ParserError::ParserError)?
|
|
||||||
.trim_end_matches(']'); // just in case
|
|
||||||
|
|
||||||
let level = match level_str {
|
|
||||||
"INFO" => LogLevel::Info,
|
|
||||||
"WARN" => LogLevel::Warn,
|
|
||||||
"ERROR" => LogLevel::Error,
|
|
||||||
_ => LogLevel::Other,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(LogMeta {
|
|
||||||
time,
|
|
||||||
thread,
|
|
||||||
level,
|
|
||||||
msg,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mc-vanilla")]
|
|
||||||
impl Display for LogMeta {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let line = format!(
|
|
||||||
"Time: {}\nThread: {}\nLevel: {}\nMessage: {}",
|
|
||||||
self.time, self.thread, self.level, self.msg
|
|
||||||
);
|
|
||||||
|
|
||||||
write!(f, "{}", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mc-vanilla")]
|
|
||||||
impl Display for LogLevel {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
LogLevel::Info => write!(f, "INFO"),
|
|
||||||
LogLevel::Warn => write!(f, "WARN"),
|
|
||||||
LogLevel::Error => write!(f, "ERROR"),
|
|
||||||
LogLevel::Other => write!(f, "OTHER"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
mod event;
|
|
||||||
mod line;
|
|
||||||
#[cfg(feature = "mc-vanilla")]
|
|
||||||
mod log;
|
|
||||||
|
|
||||||
pub use event::InternalEvent;
|
|
||||||
pub use event::{EventPayload, InstanceEvent};
|
|
||||||
pub use line::{StreamLine, StreamSource};
|
|
||||||
#[cfg(feature = "mc-vanilla")]
|
|
||||||
pub use log::{LogLevel, LogMeta};
|
|
||||||
@@ -1,142 +1,23 @@
|
|||||||
use std::{
|
#[derive(Debug, Clone)]
|
||||||
fmt::{self, Display},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tokio::sync::watch;
|
|
||||||
|
|
||||||
use crate::error::VersionError;
|
|
||||||
|
|
||||||
/// Identifies the type of Minecraft distribution supported by the configuration.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
|
||||||
pub enum MinecraftType {
|
|
||||||
Vanilla,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Semantic release version parsed from strings like `1.20.4`.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
pub major: u32,
|
major: u32,
|
||||||
pub minor: u32,
|
minor: u32,
|
||||||
pub patch: u32,
|
patch: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Snapshot version parsed from strings like `23w13b`.
|
#[derive(Debug, Clone)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
|
||||||
pub struct Snapshot {
|
|
||||||
pub year: u32,
|
|
||||||
pub week: u32,
|
|
||||||
pub build: char,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enum covering both release and snapshot Minecraft version formats.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
|
||||||
pub enum MinecraftVersion {
|
pub enum MinecraftVersion {
|
||||||
|
Unknown,
|
||||||
Release(Version),
|
Release(Version),
|
||||||
Snapshot(Snapshot),
|
#[cfg(feature = "version-custom")]
|
||||||
|
Custom(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Version {
|
#[derive(Debug, Clone)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
pub enum MinecraftType {
|
||||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
Unknown,
|
||||||
}
|
#[cfg(feature = "mc-vanilla")]
|
||||||
}
|
Vanilla,
|
||||||
|
#[cfg(feature = "mc-paper")]
|
||||||
impl Display for Snapshot {
|
Paper,
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}w{:02}{}", self.year, self.week, self.build)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for MinecraftVersion {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
MinecraftVersion::Release(version) => write!(f, "{}", version.to_string()),
|
|
||||||
MinecraftVersion::Snapshot(snapshot) => write!(f, "{}", snapshot.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Version {
|
|
||||||
type Err = VersionError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut split = s.split('.');
|
|
||||||
|
|
||||||
let major_str = split.next().ok_or(VersionError::MissingMajor)?;
|
|
||||||
let minor_str = split.next().ok_or(VersionError::MissingMinor)?;
|
|
||||||
let patch_str = split.next().ok_or(VersionError::MissingPatch)?;
|
|
||||||
|
|
||||||
if split.next().is_some() {
|
|
||||||
return Err(VersionError::ExtraComponents);
|
|
||||||
}
|
|
||||||
|
|
||||||
let major = major_str
|
|
||||||
.parse::<u32>()
|
|
||||||
.map_err(|_| VersionError::IncorrectMajor(major_str.to_string()))?;
|
|
||||||
|
|
||||||
let minor = minor_str
|
|
||||||
.parse::<u32>()
|
|
||||||
.map_err(|_| VersionError::IncorrectMinor(minor_str.to_string()))?;
|
|
||||||
|
|
||||||
let patch = patch_str
|
|
||||||
.parse::<u32>()
|
|
||||||
.map_err(|_| VersionError::IncorrectPatch(patch_str.to_string()))?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
major,
|
|
||||||
minor,
|
|
||||||
patch,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Snapshot {
|
|
||||||
type Err = VersionError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let (year_str, rest) = s
|
|
||||||
.split_once('w')
|
|
||||||
.ok_or(VersionError::InvalidSnapshotFormat)?;
|
|
||||||
|
|
||||||
if rest.len() < 3 {
|
|
||||||
return Err(VersionError::InvalidSnapshotFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
let week_str = &rest[..2];
|
|
||||||
let build_str = &rest[2..];
|
|
||||||
|
|
||||||
let year = year_str
|
|
||||||
.parse::<u32>()
|
|
||||||
.map_err(|_| VersionError::IncorrectYear(year_str.to_string()))?;
|
|
||||||
|
|
||||||
let week = week_str
|
|
||||||
.parse::<u32>()
|
|
||||||
.map_err(|_| VersionError::IncorrectWeek(week_str.to_string()))?;
|
|
||||||
|
|
||||||
let build = if build_str.len() == 1 {
|
|
||||||
build_str.chars().next().unwrap()
|
|
||||||
} else {
|
|
||||||
return Err(VersionError::IncorrectBuild(build_str.to_string()));
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self { year, week, build })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for MinecraftVersion {
|
|
||||||
type Err = VersionError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
if let Ok(ver) = Version::from_str(s) {
|
|
||||||
return Ok(MinecraftVersion::Release(ver));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(snap) = Snapshot::from_str(s) {
|
|
||||||
return Ok(MinecraftVersion::Snapshot(snap));
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(VersionError::UnknownVersionFormat(s.to_string()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
158
src/error.rs
158
src/error.rs
@@ -1,137 +1,33 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Error)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
|
||||||
#[error("Undefined error")]
|
|
||||||
Generic,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Error)]
|
|
||||||
pub enum VersionError {
|
|
||||||
#[error("Incorrect major version: {0}")]
|
|
||||||
IncorrectMajor(String),
|
|
||||||
|
|
||||||
#[error("Incorrect minor version: {0}")]
|
|
||||||
IncorrectMinor(String),
|
|
||||||
|
|
||||||
#[error("Incorrect patch version: {0}")]
|
|
||||||
IncorrectPatch(String),
|
|
||||||
|
|
||||||
#[error("Incorrect major version: {0}")]
|
|
||||||
IncorrectYear(String),
|
|
||||||
|
|
||||||
#[error("Incorrect minor version: {0}")]
|
|
||||||
IncorrectWeek(String),
|
|
||||||
|
|
||||||
#[error("Incorrect patch version: {0}")]
|
|
||||||
IncorrectBuild(String),
|
|
||||||
|
|
||||||
#[error("Missing major version")]
|
|
||||||
MissingMajor,
|
|
||||||
|
|
||||||
#[error("Missing minor version")]
|
|
||||||
MissingMinor,
|
|
||||||
|
|
||||||
#[error("Missing patch version")]
|
|
||||||
MissingPatch,
|
|
||||||
|
|
||||||
#[error("Invalid snapshot format")]
|
|
||||||
InvalidSnapshotFormat,
|
|
||||||
|
|
||||||
#[error("Too many components")]
|
|
||||||
ExtraComponents,
|
|
||||||
|
|
||||||
#[error("Unrecognized version format: {0}")]
|
|
||||||
UnknownVersionFormat(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Error)]
|
|
||||||
pub enum HandleError {
|
pub enum HandleError {
|
||||||
#[error("Invalid Minecraft Version: {0}")]
|
#[error("internal library error, this shouldn't happen")]
|
||||||
InvalidVersion(String),
|
InternalError,
|
||||||
|
|
||||||
#[error("Invalid server root directory: {0}")]
|
#[error("failed to start handle, not stopped")]
|
||||||
InvalidDirectory(String),
|
StartFailedNotStopped,
|
||||||
|
|
||||||
#[error("Invalid relative JAR path: {0}")]
|
#[error("failed to stop handle, not running")]
|
||||||
InvalidPathJAR(String),
|
StopFailedNotRunning,
|
||||||
}
|
|
||||||
|
#[error("failed to start handle, child exists")]
|
||||||
#[derive(Debug, Clone, Error)]
|
StartFailedChildExists,
|
||||||
pub enum SubscribeError {
|
#[error("failed to stop handle, child doesn't exists")]
|
||||||
#[error("No stdout found")]
|
StopFailedChildNotExists,
|
||||||
NoStdout,
|
|
||||||
|
#[error("failed to kill handle, child doesn't exists")]
|
||||||
#[error("No stderr found")]
|
KillFailedChildNotExists,
|
||||||
NoStderr,
|
|
||||||
}
|
#[error("failed to kill handle, internal error")]
|
||||||
|
KillFailledInternal,
|
||||||
#[derive(Debug, Clone, Error)]
|
|
||||||
pub enum ServerError {
|
#[error("failed to start pumps, child inexistant")]
|
||||||
#[error("Server is already running")]
|
PumpsFailedNoChild,
|
||||||
AlreadyRunning,
|
|
||||||
|
#[error("failed to start pumps, no stdout in child")]
|
||||||
#[error("Server is not running")]
|
PumpsFailedNoStdout,
|
||||||
NotRunning,
|
|
||||||
|
#[error("failed to start pumps, no stderr in child")]
|
||||||
#[error("Server crashed early")]
|
PumpsFailedNoStderr,
|
||||||
EarlyCrash,
|
|
||||||
|
|
||||||
#[error("Failed to run java command")]
|
|
||||||
CommandFailed,
|
|
||||||
|
|
||||||
#[error("Failed to access child stdout pipe")]
|
|
||||||
NoStdoutPipe,
|
|
||||||
|
|
||||||
#[error("Failed to access child stdin pipe")]
|
|
||||||
NoStdinPipe,
|
|
||||||
|
|
||||||
#[error("Failed to access child stderr pipe")]
|
|
||||||
NoStderrPipe,
|
|
||||||
|
|
||||||
#[error("Failed to write to stdin")]
|
|
||||||
StdinWriteFailed,
|
|
||||||
|
|
||||||
#[error("Failed to open eula.txt")]
|
|
||||||
NoEULA,
|
|
||||||
#[error("Failed to write eula.txt")]
|
|
||||||
WriteEULAFailed,
|
|
||||||
|
|
||||||
#[error("File io error")]
|
|
||||||
FileIO,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
#[derive(Debug, Clone, Error)]
|
|
||||||
pub enum ParserError {
|
|
||||||
#[error("ParserError")]
|
|
||||||
ParserError,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Error)]
|
|
||||||
pub enum CreationError {
|
|
||||||
#[error("CreationError")]
|
|
||||||
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 {
|
|
||||||
#[error("ManifestError")]
|
|
||||||
ManifestError,
|
|
||||||
|
|
||||||
#[error("Failed to load mainfest")]
|
|
||||||
LoadUrlError,
|
|
||||||
#[error("Failed to parse manifest json")]
|
|
||||||
JsonParseError,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,451 +1,231 @@
|
|||||||
use std::{path::PathBuf, process::Stdio, sync::Arc, time::Duration};
|
use std::{process::Stdio, sync::Arc};
|
||||||
|
|
||||||
use chrono::Utc;
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter},
|
io::{AsyncBufReadExt, BufReader},
|
||||||
process::{self, Child},
|
process::{Child, Command},
|
||||||
sync::{RwLock, broadcast, mpsc},
|
sync::{broadcast, RwLock},
|
||||||
time::sleep,
|
|
||||||
};
|
};
|
||||||
use tokio_stream::StreamExt;
|
|
||||||
use tokio_stream::wrappers::BroadcastStream;
|
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[cfg(feature = "events")]
|
use crate::{config::config::ServerConfig, error::HandleError, instance::types::InstanceStatus};
|
||||||
use crate::config::stream::InstanceEvent;
|
|
||||||
use crate::{
|
|
||||||
config::{
|
|
||||||
MinecraftType, MinecraftVersion, StreamSource,
|
|
||||||
stream::{EventPayload, InternalEvent},
|
|
||||||
},
|
|
||||||
error::{HandleError, ServerError, SubscribeError},
|
|
||||||
server::domain::MineGuardConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{InstanceData, InstanceStatus};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct InstanceHandle {
|
pub struct InstanceHandle {
|
||||||
pub data: InstanceData,
|
config: Arc<RwLock<ServerConfig>>,
|
||||||
pub status: Arc<RwLock<InstanceStatus>>,
|
status: Arc<RwLock<InstanceStatus>>,
|
||||||
stdout_tx: broadcast::Sender<InstanceEvent>,
|
|
||||||
stderr_tx: Option<broadcast::Sender<InstanceEvent>>,
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
events_tx: broadcast::Sender<InstanceEvent>,
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
internal_events_tx: mpsc::Sender<InstanceEvent>,
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
internal_events_rx: Option<mpsc::Receiver<InstanceEvent>>,
|
|
||||||
stdin_tx: mpsc::Sender<String>,
|
|
||||||
stdin_rx: Option<mpsc::Receiver<String>>,
|
|
||||||
child: Option<Arc<RwLock<Child>>>,
|
child: Option<Arc<RwLock<Child>>>,
|
||||||
shutdown: CancellationToken,
|
shutdown: CancellationToken,
|
||||||
internal_bus_tx: broadcast::Sender<InternalEvent>,
|
stdout_tx: broadcast::Sender<String>,
|
||||||
|
stderr_tx: broadcast::Sender<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstanceHandle {
|
impl InstanceHandle {
|
||||||
pub fn new_with_config(config: MineGuardConfig) -> Result<Self, HandleError> {
|
/// Create a new `InstanceHandle` with a blank `ServerConfig`
|
||||||
InstanceHandle::new_with_params(
|
pub fn new() -> Self {
|
||||||
config.server_dir,
|
Self {
|
||||||
config.jar_path,
|
config: Arc::new(RwLock::new(ServerConfig::new())),
|
||||||
config.mc_version,
|
status: Arc::new(RwLock::new(InstanceStatus::Stopped)),
|
||||||
config.mc_type,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pub fn new_with_params(
|
|
||||||
root_dir: PathBuf,
|
|
||||||
jar_path: PathBuf,
|
|
||||||
mc_version: MinecraftVersion,
|
|
||||||
mc_type: MinecraftType,
|
|
||||||
) -> Result<Self, HandleError> {
|
|
||||||
let parsed_version: MinecraftVersion = mc_version;
|
|
||||||
|
|
||||||
let root: PathBuf = root_dir.clone().into();
|
|
||||||
if !root.exists() || !root.is_dir() {
|
|
||||||
return Err(HandleError::InvalidDirectory(
|
|
||||||
root_dir.to_str().unwrap().to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
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_str().unwrap().to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = InstanceData {
|
|
||||||
root_dir: root,
|
|
||||||
jar_path: path,
|
|
||||||
mc_version: parsed_version,
|
|
||||||
mc_type,
|
|
||||||
};
|
|
||||||
|
|
||||||
let status = InstanceStatus::Stopped;
|
|
||||||
|
|
||||||
let (stdin_tx, stdin_rx) = mpsc::channel(1024);
|
|
||||||
let (internal_tx, internal_rx) = mpsc::channel(1024);
|
|
||||||
Ok(Self {
|
|
||||||
data,
|
|
||||||
status: Arc::new(RwLock::new(status)),
|
|
||||||
stdout_tx: broadcast::Sender::new(2048),
|
|
||||||
stderr_tx: None,
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
events_tx: broadcast::Sender::new(2048),
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
internal_events_tx: internal_tx,
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
internal_events_rx: Some(internal_rx),
|
|
||||||
stdin_tx,
|
|
||||||
stdin_rx: Some(stdin_rx),
|
|
||||||
child: None,
|
child: None,
|
||||||
shutdown: CancellationToken::new(),
|
shutdown: CancellationToken::new(),
|
||||||
internal_bus_tx: broadcast::Sender::new(2048),
|
stdout_tx: broadcast::Sender::new(1024),
|
||||||
|
stderr_tx: broadcast::Sender::new(1024),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `InstanceHandle` with a `ServerConfig`, config is consumed
|
||||||
|
pub fn with_cfg(config: ServerConfig) -> Result<Self, HandleError> {
|
||||||
|
Ok(Self {
|
||||||
|
config: Arc::new(RwLock::new(config)),
|
||||||
|
status: Arc::new(RwLock::new(InstanceStatus::Stopped)),
|
||||||
|
child: None,
|
||||||
|
shutdown: CancellationToken::new(),
|
||||||
|
stdout_tx: broadcast::Sender::new(1024),
|
||||||
|
stderr_tx: broadcast::Sender::new(1024),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_command<S: Into<String>>(&self, cmd: S) -> Result<(), ServerError> {
|
pub async fn start(&mut self) -> Result<(), HandleError> {
|
||||||
let mut command = cmd.into();
|
if !self.stopped_killed_or_crashed().await {
|
||||||
if !command.ends_with('\n') {
|
return Err(HandleError::StartFailedNotStopped);
|
||||||
command.push('\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.stdin_tx
|
|
||||||
.send(command)
|
|
||||||
.await
|
|
||||||
.map_err(|_| ServerError::StdinWriteFailed)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn start(&mut self) -> Result<(), ServerError> {
|
|
||||||
self.validate_start_parameters().await?;
|
|
||||||
self.setup_loopback()?;
|
|
||||||
|
|
||||||
self.transition_status(InstanceStatus::Starting).await;
|
|
||||||
|
|
||||||
let command = self.build_start_command();
|
|
||||||
let child = self.spawn_child_process(command)?;
|
|
||||||
|
|
||||||
self.setup_stream_pumps(child)?;
|
|
||||||
|
|
||||||
self.setup_parser()?;
|
|
||||||
|
|
||||||
let mut rx = self.internal_bus_tx.subscribe();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match rx.recv().await {
|
|
||||||
Ok(event) => {
|
|
||||||
if event == InternalEvent::ServerStarted {
|
|
||||||
self.transition_status(InstanceStatus::Running).await;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_ => continue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn validate_start_parameters(&self) -> Result<(), ServerError> {
|
|
||||||
if self.child.is_some() {
|
if self.child.is_some() {
|
||||||
return Err(ServerError::AlreadyRunning);
|
return Err(HandleError::StartFailedChildExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
_ = self.change_status(InstanceStatus::Starting);
|
||||||
|
|
||||||
|
let mut command = self.build_command().await;
|
||||||
|
let child = command.spawn();
|
||||||
|
|
||||||
|
self.setup_std_pumps().await?;
|
||||||
|
|
||||||
|
if cfg!(feature = "event") {
|
||||||
|
// TODO: await server Done (...)! before status::Running for event module
|
||||||
|
todo!()
|
||||||
|
} else {
|
||||||
|
_ = self.change_status(InstanceStatus::Running);
|
||||||
|
}
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn transition_status(&self, status: InstanceStatus) {
|
pub async fn stop(&mut self) -> Result<(), HandleError> {
|
||||||
let r_guard = self.status.read().await;
|
if self.get_status().await != InstanceStatus::Running {
|
||||||
let old = r_guard.clone();
|
return Err(HandleError::StopFailedNotRunning);
|
||||||
drop(r_guard);
|
|
||||||
|
|
||||||
let new = status.clone();
|
|
||||||
|
|
||||||
let mut guard = self.status.write().await;
|
|
||||||
*guard = status;
|
|
||||||
drop(guard);
|
|
||||||
|
|
||||||
let event = InstanceEvent {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
|
|
||||||
timestamp: Utc::now(),
|
|
||||||
|
|
||||||
payload: EventPayload::StateChange { old, new },
|
|
||||||
};
|
|
||||||
|
|
||||||
_ = self.internal_events_tx.send(event).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_start_command(&self) -> process::Command {
|
let child = self.child.clone().ok_or(HandleError::StopFailedChildNotExists)?;
|
||||||
let mut command = process::Command::new("java");
|
|
||||||
command
|
_ = self.change_status(InstanceStatus::Stopping);
|
||||||
.arg("-jar")
|
|
||||||
.arg(&self.data.jar_path)
|
let child_w = child.write().await;
|
||||||
.arg("nogui")
|
|
||||||
.current_dir(&self.data.root_dir)
|
// TODO:: Create send command for graceful stop, finish logic with shutdown token
|
||||||
.stdout(Stdio::piped())
|
todo!()
|
||||||
.stderr(Stdio::piped())
|
}
|
||||||
.stdin(Stdio::piped());
|
|
||||||
|
pub async fn kill(&mut self) -> Result<(), HandleError> {
|
||||||
|
let child = self.child.clone().ok_or(HandleError::KillFailedChildNotExists)?;
|
||||||
|
|
||||||
|
_ = self.change_status(InstanceStatus::Killing);
|
||||||
|
|
||||||
|
let mut child_w = child.write().await;
|
||||||
|
|
||||||
|
child_w.kill().await.map_err(|_| HandleError::KillFailledInternal)?;
|
||||||
|
|
||||||
|
// TODO:: Finish kill logic including updating shutdown token status
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// region: --- Utils
|
||||||
|
|
||||||
|
impl InstanceHandle {
|
||||||
|
async fn stopped_killed_or_crashed(&self) -> bool {
|
||||||
|
let status = self.get_status().await;
|
||||||
|
|
||||||
|
if status == InstanceStatus::Stopped
|
||||||
|
|| status == InstanceStatus::Killed
|
||||||
|
|| status == InstanceStatus::Crashed
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_status(&self) -> InstanceStatus {
|
||||||
|
let status_r = self.status.read().await;
|
||||||
|
let res = status_r.clone();
|
||||||
|
drop(status_r);
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn change_status(
|
||||||
|
&mut self,
|
||||||
|
new_status: InstanceStatus,
|
||||||
|
) -> (InstanceStatus, InstanceStatus) {
|
||||||
|
let mut status_w = self.status.write().await;
|
||||||
|
let old = status_w.clone();
|
||||||
|
*status_w = new_status.clone();
|
||||||
|
drop(status_w);
|
||||||
|
(old, new_status)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_config(&self) -> ServerConfig {
|
||||||
|
let config_r = self.config.read().await;
|
||||||
|
let res = config_r.clone();
|
||||||
|
drop(config_r);
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn build_command(&self) -> Command {
|
||||||
|
let cfg = self.get_config().await;
|
||||||
|
let mut command = Command::new("java");
|
||||||
|
|
||||||
|
command.arg("-jar").arg(&cfg.jar_path).arg("nogui").current_dir(&cfg.core_path);
|
||||||
|
|
||||||
|
command.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||||
|
|
||||||
command.process_group(0);
|
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_child_process(&self, mut command: process::Command) -> Result<Child, ServerError> {
|
|
||||||
command.spawn().map_err(|_| ServerError::CommandFailed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_stream_pumps(&mut self, mut child: Child) -> Result<(), ServerError> {
|
async fn get_status_arc(arc: Arc<RwLock<InstanceStatus>>) -> InstanceStatus {
|
||||||
let stdout = child.stdout.take().ok_or(ServerError::NoStdoutPipe)?;
|
let status_r = arc.read().await;
|
||||||
let stderr = child.stderr.take().ok_or(ServerError::NoStderrPipe)?;
|
let res = status_r.clone();
|
||||||
let stdin = child.stdin.take().ok_or(ServerError::NoStdinPipe)?;
|
drop(status_r);
|
||||||
|
|
||||||
let child = Arc::new(RwLock::new(child));
|
res
|
||||||
self.child = Some(child);
|
}
|
||||||
|
|
||||||
|
async fn change_status_arc(
|
||||||
|
arc: Arc<RwLock<InstanceStatus>>,
|
||||||
|
new_status: InstanceStatus,
|
||||||
|
) -> (InstanceStatus, InstanceStatus) {
|
||||||
|
let mut status_w = arc.write().await;
|
||||||
|
let old = status_w.clone();
|
||||||
|
*status_w = new_status.clone();
|
||||||
|
drop(status_w);
|
||||||
|
(old, new_status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion: --- Utils
|
||||||
|
|
||||||
|
// region: --- StdStream
|
||||||
|
impl InstanceHandle {
|
||||||
|
async fn setup_std_pumps(&mut self) -> Result<(), HandleError> {
|
||||||
|
let child = self.child.clone().ok_or(HandleError::PumpsFailedNoChild)?;
|
||||||
|
|
||||||
|
let mut child_w = child.write().await;
|
||||||
|
|
||||||
|
let stdout_rx = child_w.stdout.take().ok_or(HandleError::PumpsFailedNoStdout)?;
|
||||||
|
let stderr_rx = child_w.stderr.take().ok_or(HandleError::PumpsFailedNoStderr)?;
|
||||||
|
|
||||||
let stdout_tx = self.stdout_tx.clone();
|
let stdout_tx = self.stdout_tx.clone();
|
||||||
let stderr_tx = broadcast::Sender::new(2048);
|
let stderr_tx = self.stderr_tx.clone();
|
||||||
self.stderr_tx = Some(stderr_tx.clone());
|
|
||||||
let shutdown = self.shutdown.clone();
|
|
||||||
|
|
||||||
let stdout_status = self.status.clone();
|
let status_stdout = self.status.clone();
|
||||||
let stderr_status = self.status.clone();
|
let status_stderr = self.status.clone();
|
||||||
let internal_tx1 = self.internal_events_tx.clone();
|
|
||||||
let internal_tx2 = self.internal_events_tx.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut stdout_reader = BufReader::new(stdout).lines();
|
let mut stdout_br = BufReader::new(stdout_rx).lines();
|
||||||
loop {
|
loop {
|
||||||
match stdout_reader.next_line().await {
|
match stdout_br.next_line().await {
|
||||||
Ok(Some(line)) => {
|
Ok(Some(line)) => {
|
||||||
let _ = stdout_tx.send(InstanceEvent::stdout(line));
|
_ = stdout_tx.send(line);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let status_guard = stdout_status.read().await;
|
let status = get_status_arc(status_stdout.clone()).await;
|
||||||
let state = status_guard.clone();
|
if status == InstanceStatus::Running {
|
||||||
if state == InstanceStatus::Running && state == InstanceStatus::Starting {
|
change_status_arc(status_stdout, InstanceStatus::Crashed).await;
|
||||||
let old = status_guard.clone();
|
|
||||||
drop(status_guard);
|
|
||||||
let mut status = stdout_status.write().await;
|
|
||||||
*status = InstanceStatus::Crashed;
|
|
||||||
let event = InstanceEvent {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
|
|
||||||
timestamp: Utc::now(),
|
|
||||||
|
|
||||||
payload: EventPayload::StateChange {
|
|
||||||
old,
|
|
||||||
new: status.clone(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
_ = internal_tx1.send(event).await;
|
|
||||||
drop(status);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
drop(status_guard);
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut stderr_reader = BufReader::new(stderr).lines();
|
let mut stdout_br = BufReader::new(stderr_rx).lines();
|
||||||
loop {
|
loop {
|
||||||
match stderr_reader.next_line().await {
|
match stdout_br.next_line().await {
|
||||||
Ok(Some(line)) => {
|
Ok(Some(line)) => {
|
||||||
let _ = stderr_tx.send(InstanceEvent::stderr(line));
|
_ = stderr_tx.send(line);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let status_guard = stderr_status.read().await;
|
let status = get_status_arc(status_stderr.clone()).await;
|
||||||
let state = status_guard.clone();
|
if status == InstanceStatus::Running {
|
||||||
if state == InstanceStatus::Running && state == InstanceStatus::Starting {
|
change_status_arc(status_stderr, InstanceStatus::Crashed).await;
|
||||||
let old = status_guard.clone();
|
|
||||||
drop(status_guard);
|
|
||||||
let mut status = stderr_status.write().await;
|
|
||||||
*status = InstanceStatus::Crashed;
|
|
||||||
let event = InstanceEvent {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
|
|
||||||
timestamp: Utc::now(),
|
|
||||||
|
|
||||||
payload: EventPayload::StateChange {
|
|
||||||
old,
|
|
||||||
new: status.clone(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
_ = internal_tx2.send(event).await;
|
|
||||||
drop(status);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
drop(status_guard);
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut stdin_rx = self.stdin_rx.take().ok_or(ServerError::NoStdinPipe)?;
|
todo!()
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut writer = BufWriter::new(stdin);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
_ = shutdown.cancelled() => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
maybe_cmd = stdin_rx.recv() => {
|
|
||||||
if let Some(cmd) = maybe_cmd {
|
|
||||||
_ = writer.write_all(cmd.as_bytes()).await;
|
|
||||||
_ = writer.flush().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "events", any(feature = "mc-vanilla")))]
|
|
||||||
fn setup_loopback(&mut self) -> Result<(), ServerError> {
|
|
||||||
let shutdown1 = self.shutdown.clone();
|
|
||||||
|
|
||||||
let event_tx1 = self.events_tx.clone();
|
|
||||||
//internal mpsc to broadcast loopback
|
|
||||||
if let Some(mut internal_rx) = self.internal_events_rx.take() {
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let tx = event_tx1;
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
_ = shutdown1.cancelled() => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
maybe_event = internal_rx.recv() => {
|
|
||||||
if let Some(event) = maybe_event {
|
|
||||||
_ = tx.send(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "events", any(feature = "mc-vanilla")))]
|
|
||||||
fn setup_parser(&mut self) -> Result<(), ServerError> {
|
|
||||||
use crate::config::LogMeta;
|
|
||||||
|
|
||||||
let stdout_stream = self
|
|
||||||
.subscribe(StreamSource::Stdout)
|
|
||||||
.map_err(|_| ServerError::NoStdoutPipe)?;
|
|
||||||
let shutdown2 = self.shutdown.clone();
|
|
||||||
let bus_tx = self.internal_bus_tx.clone();
|
|
||||||
|
|
||||||
#[cfg(feature = "mc-vanilla")]
|
|
||||||
if self.data.mc_type == MinecraftType::Vanilla {
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut rx = stdout_stream;
|
|
||||||
let tx = bus_tx;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
_ = shutdown2.cancelled() => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
next_line = rx.next() => {
|
|
||||||
if let Some(Ok(val)) = next_line {
|
|
||||||
let event_line = match val.payload {
|
|
||||||
EventPayload::StdLine{line} => {
|
|
||||||
line
|
|
||||||
},
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
let meta = match LogMeta::new(event_line.line) {
|
|
||||||
Ok(Some(log_meta)) => {
|
|
||||||
log_meta
|
|
||||||
},
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
match meta.parse_event() {
|
|
||||||
Ok(Some(event)) => _ = tx.send(event),
|
|
||||||
_ => continue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn kill(&mut self) -> Result<(), ServerError> {
|
|
||||||
if let Some(child_arc) = self.child.clone() {
|
|
||||||
self.transition_status(InstanceStatus::Killing).await;
|
|
||||||
let mut child = child_arc.write().await;
|
|
||||||
|
|
||||||
child.kill().await.map_err(|_| ServerError::CommandFailed)?;
|
|
||||||
|
|
||||||
self.transition_status(InstanceStatus::Killed).await;
|
|
||||||
sleep(Duration::from_secs(1)).await;
|
|
||||||
self.shutdown.cancel();
|
|
||||||
self.child = None;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(ServerError::NotRunning)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn stop(&mut self) -> Result<(), ServerError> {
|
|
||||||
if let Some(child_arc) = self.child.clone() {
|
|
||||||
self.transition_status(InstanceStatus::Stopping).await;
|
|
||||||
|
|
||||||
_ = self.send_command("stop").await;
|
|
||||||
let mut child = child_arc.write().await;
|
|
||||||
child.wait().await.map_err(|_| ServerError::CommandFailed)?;
|
|
||||||
|
|
||||||
self.transition_status(InstanceStatus::Stopped).await;
|
|
||||||
sleep(Duration::from_secs(1)).await;
|
|
||||||
self.shutdown.cancel();
|
|
||||||
self.child = None;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(ServerError::NotRunning)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subscribe(
|
|
||||||
&self,
|
|
||||||
stream: StreamSource,
|
|
||||||
) -> Result<BroadcastStream<InstanceEvent>, SubscribeError> {
|
|
||||||
match stream {
|
|
||||||
StreamSource::Stdout => {
|
|
||||||
let rx = self.stdout_tx.subscribe();
|
|
||||||
Ok(BroadcastStream::new(rx))
|
|
||||||
}
|
|
||||||
StreamSource::Stderr => {
|
|
||||||
let rx = match &self.stderr_tx {
|
|
||||||
Some(value) => value.subscribe(),
|
|
||||||
None => return Err(SubscribeError::NoStderr),
|
|
||||||
};
|
|
||||||
Ok(BroadcastStream::new(rx))
|
|
||||||
}
|
|
||||||
#[cfg(feature = "events")]
|
|
||||||
StreamSource::Event => {
|
|
||||||
let rx = self.events_tx.subscribe();
|
|
||||||
Ok(BroadcastStream::new(rx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// endregion: --- StdStream
|
||||||
|
|||||||
@@ -1,5 +1,2 @@
|
|||||||
mod handle;
|
pub mod handle;
|
||||||
mod types;
|
pub mod types;
|
||||||
|
|
||||||
pub use handle::InstanceHandle;
|
|
||||||
pub use types::{InstanceData, InstanceStatus};
|
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::config::{MinecraftType, MinecraftVersion};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct InstanceData {
|
|
||||||
pub root_dir: PathBuf,
|
|
||||||
pub jar_path: PathBuf,
|
|
||||||
pub mc_version: MinecraftVersion,
|
|
||||||
pub mc_type: MinecraftType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum InstanceStatus {
|
pub enum InstanceStatus {
|
||||||
Starting,
|
Starting,
|
||||||
|
|||||||
11
src/lib.rs
11
src/lib.rs
@@ -1,7 +1,6 @@
|
|||||||
pub mod config;
|
mod config;
|
||||||
|
mod instance;
|
||||||
|
mod server;
|
||||||
|
|
||||||
|
pub use server::*;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod instance;
|
|
||||||
pub mod manifests;
|
|
||||||
pub mod parser;
|
|
||||||
pub mod server;
|
|
||||||
pub mod utils;
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
pub mod vanilla;
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
#![cfg(feature = "mc-vanilla")]
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use reqwest::Client;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::{config::MinecraftVersion, error::ManifestError};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
pub struct VanillaManifestV2 {
|
|
||||||
latest: VanillaManifestV2Latest,
|
|
||||||
versions: Vec<VanillaManifestV2Version>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
pub struct VanillaManifestV2Latest {
|
|
||||||
release: String,
|
|
||||||
snapshot: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
pub struct VanillaManifestV2Version {
|
|
||||||
id: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
mc_type: String,
|
|
||||||
url: String,
|
|
||||||
time: String,
|
|
||||||
#[serde(rename = "releaseTime")]
|
|
||||||
release_time: String,
|
|
||||||
sha1: String,
|
|
||||||
#[serde(rename = "complianceLevel")]
|
|
||||||
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<Self, ManifestError> {
|
|
||||||
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 {
|
|
||||||
pub async fn load() -> Result<Self, ManifestError> {
|
|
||||||
let client = Client::new();
|
|
||||||
|
|
||||||
let manifest: VanillaManifestV2 = client
|
|
||||||
.get("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json")
|
|
||||||
.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 find(
|
|
||||||
&self,
|
|
||||||
version: MinecraftVersion,
|
|
||||||
) -> Result<Option<VanillaManifestV2Version>, ManifestError> {
|
|
||||||
let id = version.to_string();
|
|
||||||
|
|
||||||
let found = match self.versions.iter().find(|p| p.id == id) {
|
|
||||||
Some(val) => Some(val.clone()),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(found)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
use regex::Regex;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
config::{
|
|
||||||
LogMeta,
|
|
||||||
stream::{EventPayload, InstanceEvent, InternalEvent, LogLevel},
|
|
||||||
},
|
|
||||||
error::ParserError,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl LogMeta {
|
|
||||||
pub fn parse_event(&self) -> Result<Option<InternalEvent>, ParserError> {
|
|
||||||
if self.thread == "Server thread" && self.level == LogLevel::Info {
|
|
||||||
return self.parse_server_thread_info_lv2();
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_server_thread_info_lv2(&self) -> Result<Option<InternalEvent>, ParserError> {
|
|
||||||
let re = Regex::new(r"Done \([0-9.]+s\)!").unwrap();
|
|
||||||
if re.is_match(&self.msg) {
|
|
||||||
return Ok(Some(InternalEvent::ServerStarted));
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
use std::{ops::RangeInclusive, path::PathBuf, str::FromStr};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::json;
|
|
||||||
use tokio::{
|
|
||||||
fs::{File, create_dir, read, read_dir},
|
|
||||||
io::{self, AsyncWriteExt},
|
|
||||||
sync::{RwLock, watch},
|
|
||||||
};
|
|
||||||
use tokio_stream::wrappers::BroadcastStream;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
config::{self, MinecraftType, MinecraftVersion, StreamSource, Version, stream::InstanceEvent},
|
|
||||||
error::{CreationError, ServerError, SubscribeError},
|
|
||||||
instance::InstanceHandle,
|
|
||||||
manifests::vanilla::{VanillaManifestV2, VanillaManifestV2Version, VanillaReleaseManifest},
|
|
||||||
server,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct MineGuardConfig {
|
|
||||||
uuid: Uuid,
|
|
||||||
pub server_dir: PathBuf,
|
|
||||||
pub jar_path: PathBuf,
|
|
||||||
pub mc_version: MinecraftVersion,
|
|
||||||
pub mc_type: MinecraftType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct MineGuardServer {
|
|
||||||
pub handle: RwLock<InstanceHandle>,
|
|
||||||
pub config: RwLock<MineGuardConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MineGuardConfig {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
uuid: Uuid::new_v4(),
|
|
||||||
server_dir: PathBuf::new(),
|
|
||||||
jar_path: PathBuf::new(),
|
|
||||||
mc_version: MinecraftVersion::Release(Version::from_str("0.00.00").unwrap()),
|
|
||||||
mc_type: MinecraftType::Vanilla,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MineGuardServer {
|
|
||||||
async fn load_cfg_handle(
|
|
||||||
config: MineGuardConfig,
|
|
||||||
handle: InstanceHandle,
|
|
||||||
) -> Result<Self, CreationError> {
|
|
||||||
Ok(Self {
|
|
||||||
config: RwLock::new(config),
|
|
||||||
handle: RwLock::new(handle),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub async fn create(
|
|
||||||
mc_version: MinecraftVersion,
|
|
||||||
mc_type: MinecraftType,
|
|
||||||
directory: PathBuf,
|
|
||||||
) -> Result<Self, CreationError> {
|
|
||||||
if !directory.is_dir() {
|
|
||||||
return Err(CreationError::DirectoryError);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 internal_dir = server_root.join(".mineguard");
|
|
||||||
create_dir(internal_dir)
|
|
||||||
.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn start(&self) -> Result<(), ServerError> {
|
|
||||||
let mut handle_w = self.handle.write().await;
|
|
||||||
let res = handle_w.start().await;
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn kill(&self) -> Result<(), ServerError> {
|
|
||||||
let mut handle_w = self.handle.write().await;
|
|
||||||
let res = handle_w.kill().await;
|
|
||||||
res
|
|
||||||
}
|
|
||||||
pub async fn stop(&self) -> Result<(), ServerError> {
|
|
||||||
let mut handle_w = self.handle.write().await;
|
|
||||||
let res = handle_w.stop().await;
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn subscribe(
|
|
||||||
&self,
|
|
||||||
stream: StreamSource,
|
|
||||||
) -> Result<BroadcastStream<InstanceEvent>, SubscribeError> {
|
|
||||||
let handle_r = self.handle.read().await;
|
|
||||||
let res = handle_r.subscribe(stream);
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn accept_eula(&self) -> Result<(), ServerError> {
|
|
||||||
let config_r = self.config.read().await;
|
|
||||||
let eula_path = config_r.server_dir.join("eula.txt");
|
|
||||||
|
|
||||||
let mut out = File::create(eula_path)
|
|
||||||
.await
|
|
||||||
.map_err(|_| ServerError::NoEULA)?;
|
|
||||||
|
|
||||||
out.write_all(b"#Generated by MineGuard\neula=true\n")
|
|
||||||
.await
|
|
||||||
.map_err(|_| ServerError::WriteEULAFailed)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn write_config(&self) -> Result<(), ServerError> {
|
|
||||||
let config_r = self.config.read().await;
|
|
||||||
let root_path = config_r.server_dir.clone();
|
|
||||||
let config_clone = config_r.clone();
|
|
||||||
drop(config_r);
|
|
||||||
|
|
||||||
let config_path = root_path.join(".mineguard/config.json");
|
|
||||||
|
|
||||||
let json = serde_json::to_vec_pretty(&config_clone).map_err(|_| ServerError::FileIO)?;
|
|
||||||
|
|
||||||
File::create(config_path)
|
|
||||||
.await
|
|
||||||
.map_err(|_| ServerError::FileIO)?
|
|
||||||
.write_all(&json)
|
|
||||||
.await
|
|
||||||
.map_err(|_| ServerError::FileIO)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn load(path: &PathBuf) -> Result<Self, CreationError> {
|
|
||||||
let config_path = path.join(".mineguard/config.json");
|
|
||||||
|
|
||||||
let data = read(config_path)
|
|
||||||
.await
|
|
||||||
.map_err(|_| CreationError::DirectoryError)?;
|
|
||||||
|
|
||||||
let config: MineGuardConfig =
|
|
||||||
serde_json::from_slice(&data).map_err(|_| CreationError::CreationError)?;
|
|
||||||
let handle = InstanceHandle::new_with_config(config.clone())
|
|
||||||
.map_err(|_| CreationError::CreationError)?;
|
|
||||||
|
|
||||||
MineGuardServer::load_cfg_handle(config, handle).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn load_all(path: PathBuf) -> Result<Vec<Self>, CreationError> {
|
|
||||||
let mut dirs = Vec::new();
|
|
||||||
let mut entries = read_dir(path)
|
|
||||||
.await
|
|
||||||
.map_err(|_| CreationError::DirectoryError)?;
|
|
||||||
|
|
||||||
while let Some(entry) = entries
|
|
||||||
.next_entry()
|
|
||||||
.await
|
|
||||||
.map_err(|_| CreationError::DirectoryError)?
|
|
||||||
{
|
|
||||||
let meta = entry
|
|
||||||
.metadata()
|
|
||||||
.await
|
|
||||||
.map_err(|_| CreationError::DirectoryError)?;
|
|
||||||
if meta.is_dir() {
|
|
||||||
dirs.push(entry.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut servers: Vec<Self> = Vec::new();
|
|
||||||
|
|
||||||
for v in dirs {
|
|
||||||
println!("{}", v.to_str().unwrap());
|
|
||||||
servers.push(Self::load(&v).await?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(servers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +1,15 @@
|
|||||||
pub mod domain;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::instance::handle::InstanceHandle;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MineGuardServer {
|
||||||
|
handle: RwLock<InstanceHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MineGuardServer {
|
||||||
|
pub fn create() -> Self {
|
||||||
|
let new_instance = InstanceHandle::new();
|
||||||
|
Self { handle: RwLock::new(new_instance) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
24
src/utils.rs
24
src/utils.rs
@@ -1,24 +0,0 @@
|
|||||||
use chrono::{DateTime, Datelike, Local, NaiveTime, TimeZone, Timelike, Utc};
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
pub fn extract_timestamp(input: &str) -> Option<DateTime<Utc>> {
|
|
||||||
let re = Regex::new(r"\[(.*?)\]").unwrap();
|
|
||||||
let time_s = re.captures(input).map(|v| v[1].to_string());
|
|
||||||
time_s.as_ref()?;
|
|
||||||
let time = NaiveTime::parse_from_str(&time_s.unwrap(), "%H:%M:%S").ok()?;
|
|
||||||
|
|
||||||
let today = Local::now().date_naive();
|
|
||||||
|
|
||||||
let local_dt = Local
|
|
||||||
.with_ymd_and_hms(
|
|
||||||
today.year(),
|
|
||||||
today.month(),
|
|
||||||
today.day(),
|
|
||||||
time.hour(),
|
|
||||||
time.minute(),
|
|
||||||
time.second(),
|
|
||||||
)
|
|
||||||
.single()?;
|
|
||||||
|
|
||||||
Some(local_dt.with_timezone(&Utc))
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user