1use crate::errors::{JavaRuntimeError, JavaRuntimeResult};
7use std::path::{Path, PathBuf};
8use std::process::Stdio;
9use tokio::io::AsyncReadExt;
10use tokio::process::{Child, Command};
11use tokio::sync::oneshot::Receiver;
12
13pub struct JavaRuntime(pub PathBuf);
15
16impl JavaRuntime {
17 pub fn new(path: PathBuf) -> Self {
19 Self(path)
20 }
21
22 pub async fn execute(&self, arguments: Vec<String>, game_dir: &Path) -> JavaRuntimeResult<Child> {
24 if !self.0.exists() {
25 return Err(JavaRuntimeError::NotFound {
26 path: self.0.clone(),
27 });
28 }
29
30 lighty_core::trace_debug!("Spawning Java process: {:?}", &self.0);
31
32 let mut command = Command::new(&self.0);
33 command
34 .current_dir(game_dir)
35 .args(arguments)
36 .stdin(Stdio::null())
37 .stdout(Stdio::piped())
38 .stderr(Stdio::piped());
39
40 #[cfg(windows)]
42 {
43 use std::os::windows::process::CommandExt;
44 const CREATE_NO_WINDOW: u32 = 0x08000000;
45 command.creation_flags(CREATE_NO_WINDOW);
46 }
47
48 let child = command.spawn()?;
49
50 lighty_core::trace_info!("Java process spawned successfully");
51 Ok(child)
52 }
53
54 pub async fn handle_io<D: Send + Sync>(
60 &self,
61 process: &mut Child,
62 on_stdout: fn(&D, &[u8]) -> JavaRuntimeResult<()>,
63 on_stderr: fn(&D, &[u8]) -> JavaRuntimeResult<()>,
64 terminator: Receiver<()>,
65 data: &D,
66 ) -> JavaRuntimeResult<()> {
67 let mut stdout = process
68 .stdout
69 .take()
70 .ok_or(JavaRuntimeError::IoCaptureFailure)?;
71 let mut stderr = process
72 .stderr
73 .take()
74 .ok_or(JavaRuntimeError::IoCaptureFailure)?;
75
76 let mut stdout_buffer = [0u8; 8192];
78 let mut stderr_buffer = [0u8; 8192];
79
80 tokio::pin!(terminator);
81
82 loop {
83 tokio::select! {
84 result = stdout.read(&mut stdout_buffer) => {
85 match result {
86 Ok(bytes_read) if bytes_read > 0 => {
87 let _ = on_stdout(data, &stdout_buffer[..bytes_read]);
88 }
89 Ok(_) => {}, Err(_) => break, }
92 },
93
94 result = stderr.read(&mut stderr_buffer) => {
95 match result {
96 Ok(bytes_read) if bytes_read > 0 => {
97 let _ = on_stderr(data, &stderr_buffer[..bytes_read]);
98 }
99 Ok(_) => {}, Err(_) => break, }
102 },
103
104 _ = &mut terminator => {
105 lighty_core::trace_debug!("Termination signal received, killing process");
106 process.kill().await?;
107 break;
108 },
109
110 exit_result = process.wait() => {
111 let exit_status = exit_result?;
112 let exit_code = exit_status.code().unwrap_or(7900);
113
114 lighty_core::trace_debug!("Java process exited with code: {}", exit_code);
115
116 if exit_code != 0 && exit_code != -1073740791 {
118 return Err(JavaRuntimeError::NonZeroExit { code: exit_code });
119 }
120
121 break;
122 },
123 }
124 }
125
126 Ok(())
127 }
128}