diff --git a/Cargo.toml b/Cargo.toml index 76aa4e2..430b48c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,13 @@ license = "MIT" publish = false [features] -default = ["core", "events"] +default = ["core", "events", "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 = [] + +mc-vanilla = [] # Add new feature groups here; attach their optional dependencies to the relevant feature list. [dependencies] diff --git a/src/config/mod.rs b/src/config/mod.rs index c34166a..b2400a1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,5 @@ pub mod stream; pub mod version; -pub use stream::{StreamLine, StreamSource}; +pub use stream::{LogMeta, StreamLine, StreamSource}; pub use version::{MinecraftType, MinecraftVersion, Snapshot, Version}; diff --git a/src/config/stream.rs b/src/config/stream.rs index 704d624..4cecedb 100644 --- a/src/config/stream.rs +++ b/src/config/stream.rs @@ -1,5 +1,8 @@ use std::fmt::{self, Display}; +#[cfg(feature = "mc-vanilla")] +use crate::error::ParserError; + /// Identifies which process stream produced a line of output. #[derive(Debug, Clone, PartialEq, Eq)] pub enum StreamSource { @@ -22,6 +25,101 @@ pub struct InstanceEvent {} #[cfg(feature = "events")] pub enum Events {} +#[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, +} + +#[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 { Self { @@ -43,6 +141,10 @@ impl StreamLine { source: StreamSource::Stderr, } } + + pub fn msg(&self) -> String { + self.line.clone() + } } impl Display for StreamLine { diff --git a/src/error.rs b/src/error.rs index a911508..22de095 100644 --- a/src/error.rs +++ b/src/error.rs @@ -92,3 +92,10 @@ pub enum ServerError { #[error("Failed to write to stdin")] StdinWriteFailed, } + +#[cfg(feature = "events")] +#[derive(Debug, Clone, Error)] +pub enum ParserError { + #[error("ParserError")] + ParserError, +} diff --git a/src/instance.rs b/src/instance.rs index 1b9a0ce..9626d36 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -74,7 +74,7 @@ impl InstanceHandle { root_dir: root, jar_path: path, mc_version: parsed_version, - mc_type: mc_type, + mc_type, }; let status = InstanceStatus::Stopped; @@ -248,32 +248,48 @@ impl InstanceHandle { Ok(()) } - #[cfg(feature = "events")] + #[cfg(all(feature = "events", any(feature = "mc-vanilla")))] fn setup_parser(&mut self) -> Result<(), ServerError> { let stdout_stream = self .subscribe(StreamSource::Stdout) .map_err(|_| ServerError::NoStdoutPipe)?; let shutdown = self.shutdown.clone(); - let event_tx = self.events_tx.clone(); + // TODO: Stream events!!!! + let _event_tx = self.events_tx.clone(); - tokio::spawn(async move { - let mut rx = stdout_stream; + #[cfg(feature = "mc-vanilla")] + if self.data.mc_type == MinecraftType::Vanilla { + use crate::config::LogMeta; + tokio::spawn(async move { + let mut rx = stdout_stream; - loop { - tokio::select! { - _ = shutdown.cancelled() => { - break; - } - line = rx.next() => { - match line { - Some(Ok(_)) => { - }, - _ => (), + loop { + tokio::select! { + _ = shutdown.cancelled() => { + break; + } + line = rx.next() => { + match line { + Some(Ok(val)) => { + let msg = val.msg(); + let meta = LogMeta::new(msg); + match meta { + Ok(val) => { + if val.is_some() { + println!("{}", val.unwrap()); + } + } + Err(_) => (), + + } + }, + _ => (), + } } } } - } - }); + }); + } Ok(()) }