server-watchdog 0.1.0

A server monitoring and remote control tool via messenger.
Documentation
use std::process::Stdio;
use derive_new::new;
use log::warn;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio_stream::wrappers::LinesStream;
use tokio::process::Command;
use tokio_stream::{Stream, StreamExt};

use super::child_process_stream::ChildProcessStream;

#[derive(new)]
pub struct SystemCommandExecutor;

impl SystemCommandExecutor {
    
    pub async fn execute(&self, cmd: &str, args: &[&str]) -> bool {
        let status = Command::new(cmd)
            .args(args)
            .status()
            .await;

        match status {
            Ok(s) => s.success(),
            Err(_) => false,
        }
    }
    
    pub async fn capture_output(&self, cmd: &str, args: &[&str]) -> Result<String, std::io::Error> {
        let output = Command::new(cmd)
            .args(args)
            .output()
            .await?;

        let stderr_output = String::from_utf8_lossy(&output.stderr);
        if !stderr_output.is_empty() {
            warn!("[{} stderr]: {}", cmd, stderr_output);
        }

        if output.status.success() {
            let mut combined = String::from_utf8_lossy(&output.stdout).to_string();
            let error_msg = String::from_utf8_lossy(&output.stderr);

            if !error_msg.is_empty() {
                combined.push_str("\n--- Error ---\n");
                combined.push_str(&error_msg);
            }
            Ok(combined)
        } else {
            Err(std::io::Error::new(std::io::ErrorKind::Other, stderr_output.to_string()))
        }
    }

    pub async fn capture_output_follow(&self, cmd: &str, args: &[&str]) -> Result<Box<dyn Stream<Item = String> + Send>, std::io::Error> {
        let mut child = Command::new(cmd)
            .args(args)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()?;

        let stdout = child.stdout.take().unwrap();
        let stderr = child.stderr.take().unwrap();

        let stdout_stream = LinesStream::new(BufReader::new(stdout).lines())
            .map(|res| res.unwrap_or_else(|e| format!("stdout error: {}", e)));
        let stderr_stream = LinesStream::new(BufReader::new(stderr).lines())
            .map(|res| res.unwrap_or_else(|e| format!("stderr error: {}", e)));

        let combined_stream = stdout_stream.merge(stderr_stream);
        let combined_stream = ChildProcessStream::new(Box::pin(combined_stream), child);
        Ok(Box::new(combined_stream))
    }
}