From d5505d3298431d42dd15da922f9338b262360dd1 Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Wed, 5 Apr 2023 10:07:59 -0700 Subject: [PATCH] Child process manager api (#64) * child process api * added hook to js * process API + restructured process state storage * formatting * added path-pid check and fixed probs * prettier * added profile checking function --------- Co-authored-by: Wyatt --- theseus/src/api/mod.rs | 4 +- theseus/src/api/process.rs | 161 +++++++++++++++++ theseus/src/api/profile.rs | 37 +--- theseus/src/api/settings.rs | 2 +- theseus/src/launcher/mod.rs | 18 +- theseus/src/state/children.rs | 214 +++++++++++++++++++++-- theseus_cli/src/subcommands/profile.rs | 2 +- theseus_gui/src-tauri/src/api/mod.rs | 4 +- theseus_gui/src-tauri/src/api/process.rs | 72 ++++++++ theseus_gui/src-tauri/src/api/profile.rs | 30 +--- theseus_gui/src-tauri/src/main.rs | 13 +- theseus_gui/src/helpers/process.js | 65 +++++++ theseus_gui/src/helpers/profile.js | 10 -- theseus_playground/src/main.rs | 39 ++++- 14 files changed, 574 insertions(+), 97 deletions(-) create mode 100644 theseus/src/api/process.rs create mode 100644 theseus_gui/src-tauri/src/api/process.rs create mode 100644 theseus_gui/src/helpers/process.js diff --git a/theseus/src/api/mod.rs b/theseus/src/api/mod.rs index cad1ffe74..eeb7a24d9 100644 --- a/theseus/src/api/mod.rs +++ b/theseus/src/api/mod.rs @@ -1,9 +1,10 @@ //! API for interacting with Theseus pub mod auth; +pub mod process; pub mod profile; pub mod profile_create; -pub mod tags; pub mod settings; +pub mod tags; pub mod data { pub use crate::state::{ @@ -16,6 +17,7 @@ pub mod prelude { pub use crate::{ auth::{self, Credentials}, data::*, + process, profile::{self, Profile}, profile_create, settings, State, }; diff --git a/theseus/src/api/process.rs b/theseus/src/api/process.rs new file mode 100644 index 000000000..3969f2559 --- /dev/null +++ b/theseus/src/api/process.rs @@ -0,0 +1,161 @@ +//! Theseus process management interface +use std::path::{Path, PathBuf}; + +use crate::state::MinecraftChild; +pub use crate::{ + state::{ + Hooks, JavaSettings, MemorySettings, Profile, Settings, WindowSize, + }, + State, +}; + +// Gets whether a child process stored in the state by PID has finished +#[tracing::instrument] +pub async fn has_finished_by_pid(pid: u32) -> crate::Result { + Ok(get_exit_status_by_pid(pid).await?.is_some()) +} + +// Gets the exit status of a child process stored in the state by PID +#[tracing::instrument] +pub async fn get_exit_status_by_pid(pid: u32) -> crate::Result> { + let state = State::get().await?; + let children = state.children.read().await; + Ok(children.exit_status(&pid).await?.and_then(|f| f.code())) +} + +// Gets the PID of each stored process in the state +#[tracing::instrument] +pub async fn get_all_pids() -> crate::Result> { + let state = State::get().await?; + let children = state.children.read().await; + Ok(children.keys()) +} + +// Gets the PID of each *running* stored process in the state +#[tracing::instrument] +pub async fn get_all_running_pids() -> crate::Result> { + let state = State::get().await?; + let children = state.children.read().await; + children.running_keys().await +} + +// Gets the Profile paths of each *running* stored process in the state +#[tracing::instrument] +pub async fn get_all_running_profile_paths() -> crate::Result> { + let state = State::get().await?; + let children = state.children.read().await; + children.running_profile_paths().await +} + +// Gets the Profiles (cloned) of each *running* stored process in the state +#[tracing::instrument] +pub async fn get_all_running_profiles() -> crate::Result> { + let state = State::get().await?; + let children = state.children.read().await; + children.running_profiles().await +} + +// Gets the PID of each stored process in the state by profile path +#[tracing::instrument] +pub async fn get_pids_by_profile_path( + profile_path: &Path, +) -> crate::Result> { + let state = State::get().await?; + let children = state.children.read().await; + children.running_keys_with_profile(profile_path).await +} + +// Gets stdout of a child process stored in the state by PID, as a string +#[tracing::instrument] +pub async fn get_stdout_by_pid(pid: u32) -> crate::Result { + let state = State::get().await?; + // Get stdout from child + let children = state.children.read().await; + + // Extract child or return crate::Error + if let Some(child) = children.get(&pid) { + let child = child.read().await; + Ok(child.stdout.get_output().await?) + } else { + Err(crate::ErrorKind::LauncherError(format!( + "No child process with PID {}", + pid + )) + .as_error()) + } +} + +// Gets stderr of a child process stored in the state by PID, as a string +#[tracing::instrument] +pub async fn get_stderr_by_pid(pid: u32) -> crate::Result { + let state = State::get().await?; + // Get stdout from child + let children = state.children.read().await; + + // Extract child or return crate::Error + if let Some(child) = children.get(&pid) { + let child = child.read().await; + Ok(child.stderr.get_output().await?) + } else { + Err(crate::ErrorKind::LauncherError(format!( + "No child process with PID {}", + pid + )) + .as_error()) + } +} + +// Kill a child process stored in the state by PID, as a string +#[tracing::instrument] +pub async fn kill_by_pid(pid: u32) -> crate::Result<()> { + let state = State::get().await?; + let children = state.children.read().await; + if let Some(mchild) = children.get(&pid) { + let mut mchild = mchild.write().await; + kill(&mut mchild).await + } else { + // No error returned for already finished process + Ok(()) + } +} + +// Wait for a child process stored in the state by PID +#[tracing::instrument] +pub async fn wait_for_by_pid(pid: u32) -> crate::Result<()> { + let state = State::get().await?; + let children = state.children.read().await; + // No error returned for already killed process + if let Some(mchild) = children.get(&pid) { + let mut mchild = mchild.write().await; + wait_for(&mut mchild).await + } else { + // No error returned for already finished process + Ok(()) + } +} + +// Kill a running child process directly, and wait for it to be killed +#[tracing::instrument] +pub async fn kill(running: &mut MinecraftChild) -> crate::Result<()> { + running.child.kill().await?; + wait_for(running).await +} + +// Await on the completion of a child process directly +#[tracing::instrument] +pub async fn wait_for(running: &mut MinecraftChild) -> crate::Result<()> { + let result = running.child.wait().await.map_err(|err| { + crate::ErrorKind::LauncherError(format!( + "Error running minecraft: {err}" + )) + })?; + + match result.success() { + false => Err(crate::ErrorKind::LauncherError(format!( + "Minecraft exited with non-zero code {}", + result.code().unwrap_or(-1) + )) + .as_error()), + true => Ok(()), + } +} diff --git a/theseus/src/api/profile.rs b/theseus/src/api/profile.rs index 9b08f4416..a16cbd7ac 100644 --- a/theseus/src/api/profile.rs +++ b/theseus/src/api/profile.rs @@ -1,4 +1,5 @@ //! Theseus profile management interface +use crate::state::MinecraftChild; pub use crate::{ state::{JavaSettings, Profile}, State, @@ -9,10 +10,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use tokio::{ - process::{Child, Command}, - sync::RwLock, -}; +use tokio::{process::Command, sync::RwLock}; /// Add a profile to the in-memory state #[tracing::instrument] @@ -114,7 +112,7 @@ pub async fn list( pub async fn run( path: &Path, credentials: &crate::auth::Credentials, -) -> crate::Result>> { +) -> crate::Result>> { let state = State::get().await.unwrap(); let settings = state.settings.read().await; let profile = get(path).await?.ok_or_else(|| { @@ -227,31 +225,8 @@ pub async fn run( "Process failed to stay open.".to_string(), ) })?; - let child_arc = state_children.insert(pid, mc_process); + let mchild_arc = + state_children.insert_process(pid, path.to_path_buf(), mc_process); - Ok(child_arc) -} - -#[tracing::instrument] -pub async fn kill(running: &mut Child) -> crate::Result<()> { - running.kill().await?; - wait_for(running).await -} - -#[tracing::instrument] -pub async fn wait_for(running: &mut Child) -> crate::Result<()> { - let result = running.wait().await.map_err(|err| { - crate::ErrorKind::LauncherError(format!( - "Error running minecraft: {err}" - )) - })?; - - match result.success() { - false => Err(crate::ErrorKind::LauncherError(format!( - "Minecraft exited with non-zero code {}", - result.code().unwrap_or(-1) - )) - .as_error()), - true => Ok(()), - } + Ok(mchild_arc) } diff --git a/theseus/src/api/settings.rs b/theseus/src/api/settings.rs index c6a2cf28a..a839f2c88 100644 --- a/theseus/src/api/settings.rs +++ b/theseus/src/api/settings.rs @@ -21,4 +21,4 @@ pub async fn set(settings: Settings) -> crate::Result<()> { // Replaces the settings struct in the RwLock with the passed argument *state.settings.write().await = settings; Ok(()) -} \ No newline at end of file +} diff --git a/theseus/src/launcher/mod.rs b/theseus/src/launcher/mod.rs index 51de34229..9fb71c92a 100644 --- a/theseus/src/launcher/mod.rs +++ b/theseus/src/launcher/mod.rs @@ -1,5 +1,5 @@ //! Logic for launching Minecraft -use crate::state as st; +use crate::{process, state as st}; use daedalus as d; use dunce::canonicalize; use std::{path::Path, process::Stdio}; @@ -176,6 +176,18 @@ pub async fn launch_minecraft( let env_args = Vec::from(env_args); + // Check if profile has a running profile, and reject running the command if it does + // Done late so a quick double call doesn't launch two instances + let existing_processes = + process::get_pids_by_profile_path(instance_path).await?; + if let Some(pid) = existing_processes.first() { + return Err(crate::ErrorKind::LauncherError(format!( + "Profile {} is already running at PID: {pid}", + instance_path.display() + )) + .as_error()); + } + command .args( args::get_jvm_arguments( @@ -217,8 +229,8 @@ pub async fn launch_minecraft( .current_dir(instance_path.clone()) .env_clear() .envs(env_args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()); + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); command.spawn().map_err(|err| { crate::ErrorKind::LauncherError(format!( diff --git a/theseus/src/state/children.rs b/theseus/src/state/children.rs index a16983015..178fc2bec 100644 --- a/theseus/src/state/children.rs +++ b/theseus/src/state/children.rs @@ -1,33 +1,166 @@ +use std::path::{Path, PathBuf}; use std::{collections::HashMap, sync::Arc}; -use tokio::process::Child; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::{ChildStderr, ChildStdout}; use tokio::sync::RwLock; +use super::Profile; + // Child processes (instances of Minecraft) -// A wrapper over a Hashmap connecting PID -> Child -// Left open for future functionality re: polling children -pub struct Children(HashMap>>); +// A wrapper over a Hashmap connecting PID -> MinecraftChild +pub struct Children(HashMap>>); + +// Minecraft Child, bundles together the PID, the actual Child, and the easily queryable stdout and stderr streams +#[derive(Debug)] +pub struct MinecraftChild { + pub pid: u32, + pub profile_path: PathBuf, //todo: make UUID when profiles are recognized by UUID + pub child: tokio::process::Child, + pub stdout: SharedOutput, + pub stderr: SharedOutput, +} impl Children { pub fn new() -> Children { Children(HashMap::new()) } - // Inserts and returns a ref to the child - // Unlike a Hashmap, this directly returns the reference to the Child rather than any previously stored Child that may exist - pub fn insert( + // Inserts a child process to keep track of, and returns a reference to the container struct MinecraftChild + // The threads for stdout and stderr are spawned here + // Unlike a Hashmap's 'insert', this directly returns the reference to the Child rather than any previously stored Child that may exist + pub fn insert_process( &mut self, pid: u32, - child: tokio::process::Child, - ) -> Arc> { - let child = Arc::new(RwLock::new(child)); - self.0.insert(pid, child.clone()); - child + profile_path: PathBuf, + mut child: tokio::process::Child, + ) -> Arc> { + // Create std watcher threads for stdout and stderr + let stdout = SharedOutput::new(); + if let Some(child_stdout) = child.stdout.take() { + let stdout_clone = stdout.clone(); + tokio::spawn(async move { + if let Err(e) = stdout_clone.read_stdout(child_stdout).await { + eprintln!("Stdout process died with error: {}", e); + } + }); + } + let stderr = SharedOutput::new(); + if let Some(child_stderr) = child.stderr.take() { + let stderr_clone = stderr.clone(); + tokio::spawn(async move { + if let Err(e) = stderr_clone.read_stderr(child_stderr).await { + eprintln!("Stderr thread died with error: {}", e); + } + }); + } + + // Create MinecraftChild + let mchild = MinecraftChild { + pid, + profile_path, + child, + stdout, + stderr, + }; + let mchild = Arc::new(RwLock::new(mchild)); + self.0.insert(pid, mchild.clone()); + mchild } // Returns a ref to the child - pub fn get(&self, pid: &u32) -> Option>> { + pub fn get(&self, pid: &u32) -> Option>> { self.0.get(pid).cloned() } + + // Gets all PID keys + pub fn keys(&self) -> Vec { + self.0.keys().cloned().collect() + } + + // Get exit status of a child by PID + // Returns None if the child is still running + pub async fn exit_status( + &self, + pid: &u32, + ) -> crate::Result> { + if let Some(child) = self.get(pid) { + let child = child.clone(); + let mut child = child.write().await; + Ok(child.child.try_wait()?) + } else { + Ok(None) + } + } + + // Gets all PID keys of running children + pub async fn running_keys(&self) -> crate::Result> { + let mut keys = Vec::new(); + for key in self.keys() { + if let Some(child) = self.get(&key) { + let child = child.clone(); + let mut child = child.write().await; + if child.child.try_wait()?.is_none() { + keys.push(key); + } + } + } + Ok(keys) + } + + // Gets all PID keys of running children with a given profile path + pub async fn running_keys_with_profile( + &self, + profile_path: &Path, + ) -> crate::Result> { + let running_keys = self.running_keys().await?; + let mut keys = Vec::new(); + for key in running_keys { + if let Some(child) = self.get(&key) { + let child = child.clone(); + let child = child.read().await; + if child.profile_path == profile_path { + keys.push(key); + } + } + } + Ok(keys) + } + + // Gets all profiles of running children + pub async fn running_profile_paths(&self) -> crate::Result> { + let mut profiles = Vec::new(); + for key in self.keys() { + if let Some(child) = self.get(&key) { + let child = child.clone(); + let mut child = child.write().await; + if child.child.try_wait()?.is_none() { + profiles.push(child.profile_path.clone()); + } + } + } + Ok(profiles) + } + + // Gets all profiles of running children + // Returns clones because it would be serialized anyway + pub async fn running_profiles(&self) -> crate::Result> { + let mut profiles = Vec::new(); + for key in self.keys() { + if let Some(child) = self.get(&key) { + let child = child.clone(); + let mut child = child.write().await; + if child.child.try_wait()?.is_none() { + if let Some(prof) = + crate::api::profile::get(&child.profile_path.clone()) + .await? + { + profiles.push(prof); + } + } + } + } + Ok(profiles) + } } impl Default for Children { @@ -35,3 +168,58 @@ impl Default for Children { Self::new() } } + +// SharedOutput, a wrapper around a String that can be read from and written to concurrently +// Designed to be used with ChildStdout and ChildStderr in a tokio thread to have a simple String storage for the output of a child process +#[derive(Clone, Debug)] +pub struct SharedOutput { + output: Arc>, +} + +impl SharedOutput { + fn new() -> Self { + SharedOutput { + output: Arc::new(RwLock::new(String::new())), + } + } + + // Main entry function to a created SharedOutput, returns the log as a String + pub async fn get_output(&self) -> crate::Result { + let output = self.output.read().await; + Ok(output.clone()) + } + + async fn read_stdout( + &self, + child_stdout: ChildStdout, + ) -> crate::Result<()> { + let mut buf_reader = BufReader::new(child_stdout); + let mut line = String::new(); + + while buf_reader.read_line(&mut line).await? > 0 { + { + let mut output = self.output.write().await; + output.push_str(&line); + } + line.clear(); + } + Ok(()) + } + + async fn read_stderr( + &self, + child_stderr: ChildStderr, + ) -> crate::Result<()> { + let mut buf_reader = BufReader::new(child_stderr); + let mut line = String::new(); + + while buf_reader.read_line(&mut line).await? > 0 { + { + let mut output = self.output.write().await; + output.push_str(&line); + } + line.clear(); + } + Ok(()) + } +} diff --git a/theseus_cli/src/subcommands/profile.rs b/theseus_cli/src/subcommands/profile.rs index 8d3de74fe..7acdc2097 100644 --- a/theseus_cli/src/subcommands/profile.rs +++ b/theseus_cli/src/subcommands/profile.rs @@ -405,7 +405,7 @@ impl ProfileRun { let proc_lock = profile::run(&path, &credentials).await?; let mut proc = proc_lock.write().await; - profile::wait_for(&mut proc).await?; + process::wait_for(&mut proc).await?; success!("Process exited successfully!"); Ok(()) diff --git a/theseus_gui/src-tauri/src/api/mod.rs b/theseus_gui/src-tauri/src/api/mod.rs index 6e26cc51e..b19a64138 100644 --- a/theseus_gui/src-tauri/src/api/mod.rs +++ b/theseus_gui/src-tauri/src/api/mod.rs @@ -3,10 +3,12 @@ use serde::{Serialize, Serializer}; use thiserror::Error; pub mod auth; + +pub mod process; pub mod profile; pub mod profile_create; -pub mod tags; pub mod settings; +pub mod tags; pub type Result = std::result::Result; diff --git a/theseus_gui/src-tauri/src/api/process.rs b/theseus_gui/src-tauri/src/api/process.rs new file mode 100644 index 000000000..45b61e8f6 --- /dev/null +++ b/theseus_gui/src-tauri/src/api/process.rs @@ -0,0 +1,72 @@ +use std::path::{Path, PathBuf}; + +use crate::api::Result; +use theseus::prelude::*; + +// Checks if a process has finished by process PID +#[tauri::command] +pub async fn process_has_finished_by_pid(pid: u32) -> Result { + Ok(process::has_finished_by_pid(pid).await?) +} + +// Gets process exit status by process PID +#[tauri::command] +pub async fn process_get_exit_status_by_pid(pid: u32) -> Result> { + Ok(process::get_exit_status_by_pid(pid).await?) +} + +// Gets all process PIDs +#[tauri::command] +pub async fn process_get_all_pids() -> Result> { + Ok(process::get_all_pids().await?) +} + +// Gets all running process PIDs +#[tauri::command] +pub async fn process_get_all_running_pids() -> Result> { + Ok(process::get_all_running_pids().await?) +} + +// Gets all process PIDs by profile path +#[tauri::command] +pub async fn process_get_pids_by_profile_path( + profile_path: &Path, +) -> Result> { + Ok(process::get_pids_by_profile_path(profile_path).await?) +} + +// Gets the Profile paths of each *running* stored process in the state +#[tauri::command] +pub async fn process_get_all_running_profile_paths() -> Result> { + Ok(process::get_all_running_profile_paths().await?) +} + +// Gets the Profiles (cloned) of each *running* stored process in the state +#[tauri::command] +pub async fn process_get_all_running_profiles() -> Result> { + Ok(process::get_all_running_profiles().await?) +} + +// Gets process stderr by process PID +#[tauri::command] +pub async fn process_get_stderr_by_pid(pid: u32) -> Result { + Ok(process::get_stderr_by_pid(pid).await?) +} + +// Gets process stdout by process PID +#[tauri::command] +pub async fn process_get_stdout_by_pid(pid: u32) -> Result { + Ok(process::get_stdout_by_pid(pid).await?) +} + +// Kill a process by process PID +#[tauri::command] +pub async fn process_kill_by_pid(pid: u32) -> Result<()> { + Ok(process::kill_by_pid(pid).await?) +} + +// Wait for a process to finish by process PID +#[tauri::command] +pub async fn process_wait_for_by_pid(pid: u32) -> Result<()> { + Ok(process::wait_for_by_pid(pid).await?) +} diff --git a/theseus_gui/src-tauri/src/api/profile.rs b/theseus_gui/src-tauri/src/api/profile.rs index b08726305..bf0687afb 100644 --- a/theseus_gui/src-tauri/src/api/profile.rs +++ b/theseus_gui/src-tauri/src/api/profile.rs @@ -76,7 +76,7 @@ pub async fn profile_run( credentials: theseus::auth::Credentials, ) -> Result { let proc_lock = profile::run(path, &credentials).await?; - let pid = proc_lock.read().await.id().ok_or_else(|| { + let pid = proc_lock.read().await.child.id().ok_or_else(|| { theseus::Error::from(theseus::ErrorKind::LauncherError( "Process failed to stay open.".to_string(), )) @@ -93,31 +93,5 @@ pub async fn profile_run_wait( ) -> Result<()> { let proc_lock = profile::run(path, &credentials).await?; let mut proc = proc_lock.write().await; - Ok(profile::wait_for(&mut proc).await?) -} - -// Wait for a running minecraft process (a Child) -// invoke('profile_wait_for', pid) -#[tauri::command] -pub async fn profile_wait_for(pid: u32) -> Result<()> { - let st = State::get().await?; - if let Some(proc_lock) = st.children.read().await.get(&pid) { - let mut proc = proc_lock.write().await; - return Ok(profile::wait_for(&mut proc).await?); - } - // If child is gone from state, it's not tracked or already finished - Ok(()) -} - -// Tries to kill a running minecraft process (if PID is still stored) -// invoke('profile_kill', pid) -#[tauri::command] -pub async fn profile_kill(pid: u32) -> Result<()> { - let st = State::get().await?; - if let Some(proc_lock) = st.children.read().await.get(&pid) { - let mut proc = proc_lock.write().await; - return Ok(profile::kill(&mut proc).await?); - } - // If child is gone from state, it's not tracked or already finished - Ok(()) + Ok(process::wait_for(&mut proc).await?) } diff --git a/theseus_gui/src-tauri/src/main.rs b/theseus_gui/src-tauri/src/main.rs index 14c24a8a5..6857af988 100644 --- a/theseus_gui/src-tauri/src/main.rs +++ b/theseus_gui/src-tauri/src/main.rs @@ -29,8 +29,6 @@ fn main() { api::profile::profile_list, api::profile::profile_run, api::profile::profile_run_wait, - api::profile::profile_kill, - api::profile::profile_wait_for, api::auth::auth_authenticate_begin_flow, api::auth::auth_authenticate_await_completion, api::auth::auth_refresh, @@ -47,6 +45,17 @@ fn main() { api::tags::tags_get_tag_bundle, api::settings::settings_get, api::settings::settings_set, + api::process::process_get_all_pids, + api::process::process_get_all_running_pids, + api::process::process_get_pids_by_profile_path, + api::process::process_get_all_running_profile_paths, + api::process::process_get_all_running_profiles, + api::process::process_get_exit_status_by_pid, + api::process::process_has_finished_by_pid, + api::process::process_get_stderr_by_pid, + api::process::process_get_stdout_by_pid, + api::process::process_kill_by_pid, + api::process::process_wait_for_by_pid, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/theseus_gui/src/helpers/process.js b/theseus_gui/src/helpers/process.js new file mode 100644 index 000000000..30a395c04 --- /dev/null +++ b/theseus_gui/src/helpers/process.js @@ -0,0 +1,65 @@ +/** + * All theseus API calls return serialized values (both return values and errors); + * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, + * and deserialized into a usable JS object. + */ +import { invoke } from '@tauri-apps/api/tauri' + +/// Gets if a process has finished by PID +/// Returns bool +export async function has_finished_by_pid(pid) { + return await invoke('process_has_finished_by_pid', { pid }) +} + +/// Gets process exit status by PID +/// Returns u32 +export async function get_exit_status_by_pid(pid) { + return await invoke('process_get_exit_status_by_pid', { pid }) +} + +/// Gets all process IDs +/// Returns [u32] +export async function get_all_pids() { + return await invoke('process_get_all_pids') +} + +/// Gets all running process IDs +/// Returns [u32] +export async function get_all_running_pids() { + return await invoke('process_get_all_running_pids') +} + +/// Gets all running process IDs with a given profile path +/// Returns [u32] +export async function get_pids_by_profile_path(profile_path) { + return await invoke('process_get_pids_by_profile_path', { profile_path }) +} + +/// Gets all running process IDs with a given profile path +/// Returns [u32] +export async function get_all_running_profile_paths(profile_path) { + return await invoke('process_get_all_running_profile_paths', { profile_path }) +} + +/// Gets all running process IDs with a given profile path +/// Returns [u32] +export async function get_all_running_profiles(profile_path) { + return await invoke('process_get_all_running_profiles', { profile_path }) +} + +/// Gets process stderr by PID +/// Returns String +export async function get_stderr_by_pid(pid) { + return await invoke('process_get_stderr_by_pid', { pid }) +} + +/// Gets process stdout by PID +/// Returns String +export async function get_stdout_by_pid(pid) { + return await invoke('process_get_stdout_by_pid', { pid }) +} + +/// Kills a process by PID +export async function kill_by_pid(pid) { + return await invoke('process_kill_by_pid', { pid }) +} diff --git a/theseus_gui/src/helpers/profile.js b/theseus_gui/src/helpers/profile.js index d1594f2a9..c9fbb58f6 100644 --- a/theseus_gui/src/helpers/profile.js +++ b/theseus_gui/src/helpers/profile.js @@ -66,13 +66,3 @@ export async function run(path, credentials) { export async function run_wait(path, credentials) { return await invoke('profile_run_wait', { path, credentials }) } - -// Tries to kill a running minecraft process (if PID is still stored) -export async function kill(child_pid) { - return await invoke('profile_kill', { child_pid }) -} - -// Wait for a running minecraft process (a Child) -export async function wait_for(child_pid) { - return await invoke('profile_wait_for', { child_pid }) -} diff --git a/theseus_playground/src/main.rs b/theseus_playground/src/main.rs index f479cb496..4f86ae3f2 100644 --- a/theseus_playground/src/main.rs +++ b/theseus_playground/src/main.rs @@ -6,8 +6,7 @@ use dunce::canonicalize; use std::path::Path; use theseus::{prelude::*, profile_create::profile_create}; -use tokio::process::Child; -use tokio::sync::RwLockWriteGuard; +use tokio::time::{sleep, Duration}; // A simple Rust implementation of the authentication run // 1) call the authenticate_begin_flow() function to get the URL to open (like you would in the frontend) @@ -117,10 +116,38 @@ async fn main() -> theseus::Result<()> { profile::run(&canonicalize(&profile_path)?, credentials).await } }?; - // Spawn a thread and hold the lock to the process until it ends - println!("Started Minecraft. Waiting for process to end..."); - let mut proc: RwLockWriteGuard = proc_lock.write().await; - profile::wait_for(&mut proc).await?; + + let pid = proc_lock + .read() + .await + .child + .id() + .expect("Could not get PID from process."); + println!("Minecraft PID: {}", pid); + + // Wait 5 seconds + println!("Waiting 10 seconds to gather logs..."); + sleep(Duration::from_secs(10)).await; + let stdout = process::get_stdout_by_pid(pid).await?; + println!("Logs after 5sec <<< {stdout} >>> end stdout"); + + println!( + "All running process PIDs {:?}", + process::get_all_running_pids().await? + ); + println!( + "All running process paths {:?}", + process::get_all_running_profile_paths().await? + ); + println!( + "All running process profiles {:?}", + process::get_all_running_profiles().await? + ); + + // hold the lock to the process until it ends + println!("Waiting for process to end..."); + let mut proc = proc_lock.write().await; + process::wait_for(&mut proc).await?; Ok(()) }