From a1f72ba842e68ae54d398401000360d5b0c65506 Mon Sep 17 00:00:00 2001 From: Hector van der Aa <103751865+h3cx@users.noreply.github.com> Date: Fri, 5 Dec 2025 20:11:08 +0100 Subject: [PATCH] Refactor module layout for clarity --- src/config/stream.rs | 249 ------------------------ src/config/stream/event.rs | 78 ++++++++ src/config/stream/line.rs | 76 ++++++++ src/config/stream/log.rs | 98 ++++++++++ src/config/stream/mod.rs | 9 + src/{instance.rs => instance/handle.rs} | 22 +-- src/instance/mod.rs | 5 + src/instance/types.rs | 22 +++ 8 files changed, 290 insertions(+), 269 deletions(-) delete mode 100644 src/config/stream.rs create mode 100644 src/config/stream/event.rs create mode 100644 src/config/stream/line.rs create mode 100644 src/config/stream/log.rs create mode 100644 src/config/stream/mod.rs rename src/{instance.rs => instance/handle.rs} (97%) create mode 100644 src/instance/mod.rs create mode 100644 src/instance/types.rs diff --git a/src/config/stream.rs b/src/config/stream.rs deleted file mode 100644 index d691ec8..0000000 --- a/src/config/stream.rs +++ /dev/null @@ -1,249 +0,0 @@ -use std::fmt::{self, Display}; - -#[cfg(feature = "events")] -use chrono::{DateTime, Utc}; -use chrono::{Local, NaiveTime, TimeZone}; -use regex::Regex; -#[cfg(feature = "events")] -use uuid::Uuid; - -#[cfg(feature = "mc-vanilla")] -use crate::error::ParserError; -use crate::instance::InstanceStatus; - -/// Identifies which process stream produced a line of output. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum StreamSource { - Stdout, - Stderr, - #[cfg(feature = "events")] - Event, -} - -/// Captures a single line of process output along with its origin stream. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct StreamLine { - line: String, - source: StreamSource, -} - -#[cfg(feature = "mc-vanilla")] -pub struct LogMeta { - time: String, - thread: String, - level: LogLevel, - msg: String, -} - -#[cfg(feature = "mc-vanilla")] -pub enum LogLevel { - Info, - Warn, - Error, - Other, -} - -#[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: DateTime, - - pub payload: EventPayload, -} - -#[cfg(feature = "mc-vanilla")] -impl LogMeta { - pub fn new>(line: S) -> Result, 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"), - } - } -} - -impl StreamLine { - pub fn new>(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>(line: S) -> Self { - let line = line.into(); - let re = Regex::new(r#"^\[[^\]]*\]\s*\[[^\]]*\]:\s*"#).unwrap(); - let line = re.replace(&line, "").to_string(); - Self { - line, - source: StreamSource::Stdout, - } - } - - pub fn stderr>(line: S) -> Self { - let line = line.into(); - let re = Regex::new(r#"^\[[^\]]*\]\s*\[[^\]]*\]:\s*"#).unwrap(); - let line = re.replace(&line, "").to_string(); - Self { - line, - source: StreamSource::Stderr, - } - } - - pub fn msg(&self) -> String { - self.line.clone() - } - - pub fn extract_timestamp(&self) -> Option> { - 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 InstanceEvent { - pub fn stdout>(line: S) -> Self { - let line = line.into(); - let s_line = StreamLine::stdout(line); - let timestamp = s_line.extract_timestamp().unwrap_or(Utc::now()); - let payload = EventPayload::StdLine { line: s_line }; - - Self { - id: Uuid::new_v4(), - timestamp, - payload, - } - } - pub fn stderr>(line: S) -> Self { - let line = line.into(); - let s_line = StreamLine::stderr(line); - let timestamp = s_line.extract_timestamp().unwrap_or(Utc::now()); - let payload = EventPayload::StdLine { line: s_line }; - - Self { - id: Uuid::new_v4(), - timestamp, - payload, - } - } -} - -impl Display for StreamLine { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.line) - } -} - -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) - } - } - } -} diff --git a/src/config/stream/event.rs b/src/config/stream/event.rs new file mode 100644 index 0000000..a1eb4f7 --- /dev/null +++ b/src/config/stream/event.rs @@ -0,0 +1,78 @@ +use std::fmt::{self, Display}; + +use uuid::Uuid; + +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, + + pub payload: EventPayload, +} + +impl InstanceEvent { + pub fn stdout>(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>(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, + } + } +} + +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) + } + } + } +} diff --git a/src/config/stream/line.rs b/src/config/stream/line.rs new file mode 100644 index 0000000..bf715c1 --- /dev/null +++ b/src/config/stream/line.rs @@ -0,0 +1,76 @@ +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 { + line: String, + source: StreamSource, +} + +impl StreamLine { + pub fn new>(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>(line: S) -> Self { + let line = line.into(); + let re = Regex::new(r#"^\[[^\]]*\]\s*\[[^\]]*\]:\s*"#).unwrap(); + let line = re.replace(&line, "").to_string(); + Self { + line, + source: StreamSource::Stdout, + } + } + + pub fn stderr>(line: S) -> Self { + let line = line.into(); + let re = Regex::new(r#"^\[[^\]]*\]\s*\[[^\]]*\]:\s*"#).unwrap(); + let line = re.replace(&line, "").to_string(); + Self { + line, + source: StreamSource::Stderr, + } + } + + pub fn msg(&self) -> String { + self.line.clone() + } + + pub fn extract_timestamp(&self) -> Option> { + 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) + } +} diff --git a/src/config/stream/log.rs b/src/config/stream/log.rs new file mode 100644 index 0000000..39c464f --- /dev/null +++ b/src/config/stream/log.rs @@ -0,0 +1,98 @@ +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")] +pub enum LogLevel { + Info, + Warn, + Error, + Other, +} + +#[cfg(feature = "mc-vanilla")] +impl LogMeta { + pub fn new>(line: S) -> Result, 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"), + } + } +} diff --git a/src/config/stream/mod.rs b/src/config/stream/mod.rs new file mode 100644 index 0000000..4adb8d2 --- /dev/null +++ b/src/config/stream/mod.rs @@ -0,0 +1,9 @@ +mod event; +mod line; +#[cfg(feature = "mc-vanilla")] +mod log; + +pub use event::{EventPayload, InstanceEvent}; +pub use line::{StreamLine, StreamSource}; +#[cfg(feature = "mc-vanilla")] +pub use log::{LogLevel, LogMeta}; diff --git a/src/instance.rs b/src/instance/handle.rs similarity index 97% rename from src/instance.rs rename to src/instance/handle.rs index 1ec6017..108cea2 100644 --- a/src/instance.rs +++ b/src/instance/handle.rs @@ -7,6 +7,7 @@ use tokio::{ sync::{RwLock, broadcast, mpsc}, time::sleep, }; +use tokio_stream::StreamExt; use tokio_stream::wrappers::BroadcastStream; use tokio_util::sync::CancellationToken; use uuid::Uuid; @@ -18,26 +19,7 @@ use crate::{ error::{HandleError, ServerError, SubscribeError}, }; -use tokio_stream::StreamExt; - -#[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)] -pub enum InstanceStatus { - Starting, - Running, - Stopping, - Stopped, - Crashed, - Killing, - Killed, -} +use super::{InstanceData, InstanceStatus}; #[derive(Debug)] pub struct InstanceHandle { diff --git a/src/instance/mod.rs b/src/instance/mod.rs new file mode 100644 index 0000000..113958c --- /dev/null +++ b/src/instance/mod.rs @@ -0,0 +1,5 @@ +mod handle; +mod types; + +pub use handle::InstanceHandle; +pub use types::{InstanceData, InstanceStatus}; diff --git a/src/instance/types.rs b/src/instance/types.rs new file mode 100644 index 0000000..98bef48 --- /dev/null +++ b/src/instance/types.rs @@ -0,0 +1,22 @@ +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)] +pub enum InstanceStatus { + Starting, + Running, + Stopping, + Stopped, + Crashed, + Killing, + Killed, +}