Skip to content

Commit

Permalink
Add player joined and left notifications with player.list
Browse files Browse the repository at this point in the history
  • Loading branch information
mbround18 committed Oct 23, 2024
1 parent 5716635 commit d7d7a81
Show file tree
Hide file tree
Showing 12 changed files with 485 additions and 223 deletions.
243 changes: 114 additions & 129 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Dockerfile.valheim
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ ENV PORT="2456" \
BACKUP_LOCATION="/home/steam/backups" \
WEBHOOK_STATUS_SUCCESSFUL="1" \
WEBHOOK_STATUS_FAILED="1" \
BEPINEX_RELEASES_URL="https://thunderstore.io/api/experimental/package/denikson/BepInExPack_Valheim/"
BEPINEX_RELEASES_URL="https://thunderstore.io/api/experimental/package/denikson/BepInExPack_Valheim/" \
WEBHOOK_STATUS_JOINED="1" \
WEBHOOK_STATUS_LEFT="1"

# Copy scripts and set permissions
COPY --chmod=${EXPECTED_OCTAL} --chown=steam:steam ./src/scripts/*.sh /home/steam/scripts/
Expand Down
1 change: 1 addition & 0 deletions src/odin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ tokio = { version = "1", features = ["full"] }
notify = "6.1.1"
json-patch = "*"
cached = "0"
anyhow = "1.0.91"

[dev-dependencies]
once_cell = "1.19.0"
Expand Down
136 changes: 70 additions & 66 deletions src/odin/commands/logs.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,55 @@
use crate::notifications::enums::notification_event::NotificationEvent;
use crate::notifications::enums::player::PlayerStatus::{Joined, Left};
use crate::log_filters::{handle_launch_probes, handle_player_events};
use crate::utils::common_paths::log_directory;
use log::{error, info, warn};
use crate::utils::environment::is_env_var_truthy;
use anyhow::{Context, Result};
use log::{error, info, warn};
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
use std::fs::{read_to_string, File};
use std::io::{BufRead, Read};
use std::path::{Path, PathBuf};
use std::{error, fs};
use std::io::prelude::*;
use std::io::{BufRead, BufReader, Seek, SeekFrom};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::task;

/// Processes a line of text and generates appropriate log messages and notifications.
///
/// # Arguments
///
/// * `path` - A `PathBuf` representing the path of the file being processed.
/// * `line` - A `String` containing the line of text to be processed.
fn handle_line(path: PathBuf, line: String) {
if line.is_empty() {
/// Struct to keep track of each file's state, including its last read line position.
#[derive(Clone)]
struct FileTracker {
path: PathBuf,
last_position: u64,
}

impl FileTracker {
fn new(path: PathBuf) -> Self {
Self {
path,
last_position: 0,
}
}
}

/// Processes a line of text from the log and generates appropriate log messages and notifications.
fn handle_line(path: &PathBuf, line: &str) {
if line.trim().is_empty() {
return;
}

if line.contains("[Info : Unity Log]") {
// skipping duplicate lines
return;
}

if is_env_var_truthy("PLAYER_EVENT_NOTIFICATIONS") {
if line.contains("I HAVE ARRIVED!") {


NotificationEvent::Player(Joined).send_notification(None);
}

if line.contains("Player disconnected") {
NotificationEvent::Player(Left).send_notification(None);
}
handle_player_events(line);
}

let file_name = Path::new(&path).file_name().unwrap().to_str().unwrap();
let file_name = match path.file_name().and_then(|name| name.to_str()) {
Some(name) => name,
None => {
error!("Failed to extract file name from path: {:?}", path);
return;
}
};

if line.contains("WARNING") {
warn!("[{}]: {}", file_name, line);
Expand All @@ -49,44 +59,39 @@ fn handle_line(path: PathBuf, line: String) {
info!("[{}]: {}", file_name, line);
}

if line.contains("Game server connected") {
NotificationEvent::Start(crate::notifications::enums::event_status::EventStatus::Successful)
.send_notification(None);
}

if line.contains("Steam manager on destroy") {
NotificationEvent::Stop(crate::notifications::enums::event_status::EventStatus::Successful)
.send_notification(None);
info!("The game server has been stopped");
}
}

fn read_file(file_name: String) -> Vec<u8> {
let path = Path::new(&file_name);
if !path.exists() {
return String::from("Not Found!").into();
}
let mut file_content = Vec::new();
let mut file = File::open(&file_name).expect("Unable to open file");
file.read_to_end(&mut file_content).expect("Unable to read");
file_content
handle_launch_probes(line);
}

async fn tail_file(path: PathBuf) -> Result<(), Box<dyn error::Error>> {
let mut last_line = 0;
let file = path.to_str().ok_or("Invalid file path")?;
/// Tails the given log file asynchronously, processing new lines as they are written to the file.
async fn tail_file(mut file_tracker: FileTracker) -> Result<()> {
let file = File::open(&file_tracker.path).context("Unable to open file for tailing")?;
let mut reader = BufReader::new(file);
reader
.seek(SeekFrom::Start(file_tracker.last_position))
.context("Failed to seek to start position")?;

loop {
let content = read_file(file.to_string());
let current_line = content.lines().count();

if current_line > last_line {
content
.lines()
.skip(last_line)
.for_each(|line| handle_line(path.clone(), line.unwrap_or_default()));
let mut new_lines = Vec::new();
for line_result in reader.by_ref().lines() {
match line_result {
Ok(line) => {
new_lines.push(line);
}
Err(e) => {
error!(
"Failed to read line from file '{}': {}",
file_tracker.path.display(),
e
);
}
}
}

last_line = current_line;
if !new_lines.is_empty() {
file_tracker.last_position = reader.stream_position()?;
for line in new_lines {
handle_line(&file_tracker.path, &line);
}
}

tokio::time::sleep(std::time::Duration::from_millis(100)).await;
Expand All @@ -95,10 +100,11 @@ async fn tail_file(path: PathBuf) -> Result<(), Box<dyn error::Error>> {

pub async fn watch_logs(log_path: String) {
let mut handles = Vec::new();
let mut watched_files = HashMap::new();
let mut watched_files: HashMap<PathBuf, FileTracker> = HashMap::new();
let log_path = Arc::new(log_path);

loop {
let paths = fs::read_dir(&log_path)
let paths = fs::read_dir(&*log_path)
.expect("Could not read log directory")
.filter_map(Result::ok)
.map(|entry| entry.path())
Expand All @@ -107,12 +113,10 @@ pub async fn watch_logs(log_path: String) {
for path in paths {
if path.is_file() {
watched_files.entry(path.clone()).or_insert_with(|| {
let handle = task::spawn(async move {
if let Err(e) = tail_file(path).await {
error!("Error tailing file: {:?}", e);
}
});
let tracker = FileTracker::new(path.clone());
let handle = task::spawn(tail_file(tracker.clone()));
handles.push(handle);
tracker
});
}
}
Expand All @@ -137,7 +141,7 @@ pub fn print_logs(log_path: String, lines: Option<u16>) {
.take(lines.unwrap_or(10) as usize)
.collect::<Vec<_>>();
for line in lines.iter().rev() {
handle_line(path.clone(), line.to_string());
handle_line(&path, line);
}
}
}
Expand Down
4 changes: 0 additions & 4 deletions src/odin/commands/stop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ use log::{debug, error, info};

use std::process::exit;

use crate::notifications::enums::event_status::EventStatus;
use crate::notifications::enums::notification_event::NotificationEvent;
use crate::{constants, server, utils::get_working_dir};

pub fn invoke(dry_run: bool) {
NotificationEvent::Stop(EventStatus::Running).send_notification(None);
debug!("Stopping server, directory needs to be where the server executable is located.");
info!(
"Stopping server, using working directory {}",
Expand All @@ -23,5 +20,4 @@ pub fn invoke(dry_run: bool) {
}
server::blocking_shutdown();
}
NotificationEvent::Stop(EventStatus::Successful).send_notification(None);
}
2 changes: 2 additions & 0 deletions src/odin/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ pub mod server;
pub mod steamcmd;
pub mod traits;
pub mod utils;

mod log_filters;
5 changes: 3 additions & 2 deletions src/odin/log_filters/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use regex::Regex;

mod player;
mod probes;

pub use player::handle_player_events;
pub use probes::handle_launch_probes;
Loading

0 comments on commit d7d7a81

Please sign in to comment.