You've already forked AstralRinth
forked from didirus/AstralRinth
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 <wyatt@modrinth.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
//! API for interacting with Theseus
|
//! API for interacting with Theseus
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod process;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod profile_create;
|
pub mod profile_create;
|
||||||
pub mod tags;
|
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
pub mod tags;
|
||||||
|
|
||||||
pub mod data {
|
pub mod data {
|
||||||
pub use crate::state::{
|
pub use crate::state::{
|
||||||
@@ -16,6 +17,7 @@ pub mod prelude {
|
|||||||
pub use crate::{
|
pub use crate::{
|
||||||
auth::{self, Credentials},
|
auth::{self, Credentials},
|
||||||
data::*,
|
data::*,
|
||||||
|
process,
|
||||||
profile::{self, Profile},
|
profile::{self, Profile},
|
||||||
profile_create, settings, State,
|
profile_create, settings, State,
|
||||||
};
|
};
|
||||||
|
|||||||
161
theseus/src/api/process.rs
Normal file
161
theseus/src/api/process.rs
Normal file
@@ -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<bool> {
|
||||||
|
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<Option<i32>> {
|
||||||
|
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<Vec<u32>> {
|
||||||
|
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<Vec<u32>> {
|
||||||
|
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<Vec<PathBuf>> {
|
||||||
|
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<Vec<Profile>> {
|
||||||
|
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<Vec<u32>> {
|
||||||
|
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<String> {
|
||||||
|
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<String> {
|
||||||
|
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(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
//! Theseus profile management interface
|
//! Theseus profile management interface
|
||||||
|
use crate::state::MinecraftChild;
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
state::{JavaSettings, Profile},
|
state::{JavaSettings, Profile},
|
||||||
State,
|
State,
|
||||||
@@ -9,10 +10,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{process::Command, sync::RwLock};
|
||||||
process::{Child, Command},
|
|
||||||
sync::RwLock,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Add a profile to the in-memory state
|
/// Add a profile to the in-memory state
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
@@ -114,7 +112,7 @@ pub async fn list(
|
|||||||
pub async fn run(
|
pub async fn run(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
credentials: &crate::auth::Credentials,
|
credentials: &crate::auth::Credentials,
|
||||||
) -> crate::Result<Arc<RwLock<Child>>> {
|
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
||||||
let state = State::get().await.unwrap();
|
let state = State::get().await.unwrap();
|
||||||
let settings = state.settings.read().await;
|
let settings = state.settings.read().await;
|
||||||
let profile = get(path).await?.ok_or_else(|| {
|
let profile = get(path).await?.ok_or_else(|| {
|
||||||
@@ -227,31 +225,8 @@ pub async fn run(
|
|||||||
"Process failed to stay open.".to_string(),
|
"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)
|
Ok(mchild_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(()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ pub async fn set(settings: Settings) -> crate::Result<()> {
|
|||||||
// Replaces the settings struct in the RwLock with the passed argument
|
// Replaces the settings struct in the RwLock with the passed argument
|
||||||
*state.settings.write().await = settings;
|
*state.settings.write().await = settings;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! Logic for launching Minecraft
|
//! Logic for launching Minecraft
|
||||||
use crate::state as st;
|
use crate::{process, state as st};
|
||||||
use daedalus as d;
|
use daedalus as d;
|
||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
use std::{path::Path, process::Stdio};
|
use std::{path::Path, process::Stdio};
|
||||||
@@ -176,6 +176,18 @@ pub async fn launch_minecraft(
|
|||||||
|
|
||||||
let env_args = Vec::from(env_args);
|
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
|
command
|
||||||
.args(
|
.args(
|
||||||
args::get_jvm_arguments(
|
args::get_jvm_arguments(
|
||||||
@@ -217,8 +229,8 @@ pub async fn launch_minecraft(
|
|||||||
.current_dir(instance_path.clone())
|
.current_dir(instance_path.clone())
|
||||||
.env_clear()
|
.env_clear()
|
||||||
.envs(env_args)
|
.envs(env_args)
|
||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::inherit());
|
.stderr(Stdio::piped());
|
||||||
|
|
||||||
command.spawn().map_err(|err| {
|
command.spawn().map_err(|err| {
|
||||||
crate::ErrorKind::LauncherError(format!(
|
crate::ErrorKind::LauncherError(format!(
|
||||||
|
|||||||
@@ -1,33 +1,166 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
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 tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use super::Profile;
|
||||||
|
|
||||||
// Child processes (instances of Minecraft)
|
// Child processes (instances of Minecraft)
|
||||||
// A wrapper over a Hashmap connecting PID -> Child
|
// A wrapper over a Hashmap connecting PID -> MinecraftChild
|
||||||
// Left open for future functionality re: polling children
|
pub struct Children(HashMap<u32, Arc<RwLock<MinecraftChild>>>);
|
||||||
pub struct Children(HashMap<u32, Arc<RwLock<Child>>>);
|
|
||||||
|
// 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 {
|
impl Children {
|
||||||
pub fn new() -> Children {
|
pub fn new() -> Children {
|
||||||
Children(HashMap::new())
|
Children(HashMap::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserts and returns a ref to the child
|
// Inserts a child process to keep track of, and returns a reference to the container struct MinecraftChild
|
||||||
// Unlike a Hashmap, this directly returns the reference to the Child rather than any previously stored Child that may exist
|
// The threads for stdout and stderr are spawned here
|
||||||
pub fn insert(
|
// 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,
|
&mut self,
|
||||||
pid: u32,
|
pid: u32,
|
||||||
child: tokio::process::Child,
|
profile_path: PathBuf,
|
||||||
) -> Arc<RwLock<Child>> {
|
mut child: tokio::process::Child,
|
||||||
let child = Arc::new(RwLock::new(child));
|
) -> Arc<RwLock<MinecraftChild>> {
|
||||||
self.0.insert(pid, child.clone());
|
// Create std watcher threads for stdout and stderr
|
||||||
child
|
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
|
// Returns a ref to the child
|
||||||
pub fn get(&self, pid: &u32) -> Option<Arc<RwLock<Child>>> {
|
pub fn get(&self, pid: &u32) -> Option<Arc<RwLock<MinecraftChild>>> {
|
||||||
self.0.get(pid).cloned()
|
self.0.get(pid).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gets all PID keys
|
||||||
|
pub fn keys(&self) -> Vec<u32> {
|
||||||
|
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<Option<std::process::ExitStatus>> {
|
||||||
|
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<Vec<u32>> {
|
||||||
|
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<Vec<u32>> {
|
||||||
|
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<Vec<PathBuf>> {
|
||||||
|
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<Vec<Profile>> {
|
||||||
|
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 {
|
impl Default for Children {
|
||||||
@@ -35,3 +168,58 @@ impl Default for Children {
|
|||||||
Self::new()
|
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<RwLock<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ impl ProfileRun {
|
|||||||
|
|
||||||
let proc_lock = profile::run(&path, &credentials).await?;
|
let proc_lock = profile::run(&path, &credentials).await?;
|
||||||
let mut proc = proc_lock.write().await;
|
let mut proc = proc_lock.write().await;
|
||||||
profile::wait_for(&mut proc).await?;
|
process::wait_for(&mut proc).await?;
|
||||||
|
|
||||||
success!("Process exited successfully!");
|
success!("Process exited successfully!");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ use serde::{Serialize, Serializer};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
|
||||||
|
pub mod process;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod profile_create;
|
pub mod profile_create;
|
||||||
pub mod tags;
|
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
pub mod tags;
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, TheseusGuiError>;
|
pub type Result<T> = std::result::Result<T, TheseusGuiError>;
|
||||||
|
|
||||||
|
|||||||
72
theseus_gui/src-tauri/src/api/process.rs
Normal file
72
theseus_gui/src-tauri/src/api/process.rs
Normal file
@@ -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<bool> {
|
||||||
|
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<Option<i32>> {
|
||||||
|
Ok(process::get_exit_status_by_pid(pid).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets all process PIDs
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn process_get_all_pids() -> Result<Vec<u32>> {
|
||||||
|
Ok(process::get_all_pids().await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets all running process PIDs
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn process_get_all_running_pids() -> Result<Vec<u32>> {
|
||||||
|
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<Vec<u32>> {
|
||||||
|
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<Vec<PathBuf>> {
|
||||||
|
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<Vec<Profile>> {
|
||||||
|
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<String> {
|
||||||
|
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<String> {
|
||||||
|
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?)
|
||||||
|
}
|
||||||
@@ -76,7 +76,7 @@ pub async fn profile_run(
|
|||||||
credentials: theseus::auth::Credentials,
|
credentials: theseus::auth::Credentials,
|
||||||
) -> Result<u32> {
|
) -> Result<u32> {
|
||||||
let proc_lock = profile::run(path, &credentials).await?;
|
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(
|
theseus::Error::from(theseus::ErrorKind::LauncherError(
|
||||||
"Process failed to stay open.".to_string(),
|
"Process failed to stay open.".to_string(),
|
||||||
))
|
))
|
||||||
@@ -93,31 +93,5 @@ pub async fn profile_run_wait(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let proc_lock = profile::run(path, &credentials).await?;
|
let proc_lock = profile::run(path, &credentials).await?;
|
||||||
let mut proc = proc_lock.write().await;
|
let mut proc = proc_lock.write().await;
|
||||||
Ok(profile::wait_for(&mut proc).await?)
|
Ok(process::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(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ fn main() {
|
|||||||
api::profile::profile_list,
|
api::profile::profile_list,
|
||||||
api::profile::profile_run,
|
api::profile::profile_run,
|
||||||
api::profile::profile_run_wait,
|
api::profile::profile_run_wait,
|
||||||
api::profile::profile_kill,
|
|
||||||
api::profile::profile_wait_for,
|
|
||||||
api::auth::auth_authenticate_begin_flow,
|
api::auth::auth_authenticate_begin_flow,
|
||||||
api::auth::auth_authenticate_await_completion,
|
api::auth::auth_authenticate_await_completion,
|
||||||
api::auth::auth_refresh,
|
api::auth::auth_refresh,
|
||||||
@@ -47,6 +45,17 @@ fn main() {
|
|||||||
api::tags::tags_get_tag_bundle,
|
api::tags::tags_get_tag_bundle,
|
||||||
api::settings::settings_get,
|
api::settings::settings_get,
|
||||||
api::settings::settings_set,
|
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!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
65
theseus_gui/src/helpers/process.js
Normal file
65
theseus_gui/src/helpers/process.js
Normal file
@@ -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 })
|
||||||
|
}
|
||||||
@@ -66,13 +66,3 @@ export async function run(path, credentials) {
|
|||||||
export async function run_wait(path, credentials) {
|
export async function run_wait(path, credentials) {
|
||||||
return await invoke('profile_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 })
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use theseus::{prelude::*, profile_create::profile_create};
|
use theseus::{prelude::*, profile_create::profile_create};
|
||||||
use tokio::process::Child;
|
use tokio::time::{sleep, Duration};
|
||||||
use tokio::sync::RwLockWriteGuard;
|
|
||||||
|
|
||||||
// A simple Rust implementation of the authentication run
|
// 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)
|
// 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
|
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 pid = proc_lock
|
||||||
let mut proc: RwLockWriteGuard<Child> = proc_lock.write().await;
|
.read()
|
||||||
profile::wait_for(&mut proc).await?;
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user