String settings hooks (#82)

* added theme; env change

* began refactoring

* added process hook

* now singular string for each hook

* fixed splitting by comma to by space

* profile_create function updated

* prettier

* added jre validator

* restructured so that it doesnt look like a vec

* fixed merge issue

* snake case

* resolved merge issues + added process events

* clippy, fmt

* removed unnecssary func
This commit is contained in:
Wyatt Verchere
2023-04-17 12:40:27 -07:00
committed by GitHub
parent b120b5cfa8
commit 9f40640ed8
23 changed files with 473 additions and 210 deletions

View File

@@ -1,25 +1,29 @@
use super::Profile;
use std::path::{Path, PathBuf};
use std::process::ExitStatus;
use std::{collections::HashMap, sync::Arc};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Child;
use tokio::process::Command;
use tokio::process::{ChildStderr, ChildStdout};
use tokio::sync::RwLock;
use crate::event::emit::emit_process;
use crate::event::ProcessPayloadType;
use super::Profile;
use tokio::task::JoinHandle;
use uuid::Uuid;
// Child processes (instances of Minecraft)
// A wrapper over a Hashmap connecting PID -> MinecraftChild
pub struct Children(HashMap<u32, Arc<RwLock<MinecraftChild>>>);
pub struct Children(HashMap<Uuid, Arc<RwLock<MinecraftChild>>>);
// Minecraft Child, bundles together the PID, the actual Child, and the easily queryable stdout and stderr streams
#[derive(Debug)]
pub struct MinecraftChild {
pub uuid: uuid::Uuid,
pub pid: u32,
pub uuid: Uuid,
pub profile_path: PathBuf, //todo: make UUID when profiles are recognized by UUID
pub child: tokio::process::Child,
pub manager: Option<JoinHandle<crate::Result<ExitStatus>>>, // None when future has completed and been handled
pub current_child: Arc<RwLock<Child>>,
pub stdout: SharedOutput,
pub stderr: SharedOutput,
}
@@ -29,16 +33,18 @@ impl Children {
Children(HashMap::new())
}
// Inserts a child process to keep track of, and returns a reference to the container struct MinecraftChild
// Runs the command in process, 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
// Unlike a Hashmap's 'insert', this directly returns the reference to the MinecraftChild rather than any previously stored MinecraftChild that may exist
pub async fn insert_process(
&mut self,
pid: u32,
uuid: Uuid,
profile_path: PathBuf,
mut child: tokio::process::Child,
mut mc_command: Command,
post_command: Option<Command>, // Command to run after minecraft.
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
let uuid = uuid::Uuid::new_v4();
// Takes the first element of the commands vector and spawns it
let mut child = mc_command.spawn()?;
// Create std watcher threads for stdout and stderr
let stdout = SharedOutput::new();
@@ -55,11 +61,25 @@ impl Children {
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);
eprintln!("Stderr process died with error: {}", e);
}
});
}
// Slots child into manager
let pid = child.id().ok_or_else(|| {
crate::ErrorKind::LauncherError(
"Process immediately failed, could not get PID".to_string(),
)
})?;
let current_child = Arc::new(RwLock::new(child));
let manager = Some(tokio::spawn(Self::sequential_process_manager(
uuid,
post_command,
pid,
current_child.clone(),
)));
emit_process(
uuid,
pid,
@@ -71,24 +91,88 @@ impl Children {
// Create MinecraftChild
let mchild = MinecraftChild {
uuid,
pid,
profile_path,
child,
current_child,
stdout,
stderr,
manager,
};
let mchild = Arc::new(RwLock::new(mchild));
self.0.insert(pid, mchild.clone());
self.0.insert(uuid, mchild.clone());
Ok(mchild)
}
// Spawns a new child process and inserts it into the hashmap
// Also, as the process ends, it spawns the follow-up process if it exists
// By convention, ExitStatus is last command's exit status, and we exit on the first non-zero exit status
async fn sequential_process_manager(
uuid: Uuid,
post_command: Option<Command>,
mut current_pid: u32,
current_child: Arc<RwLock<Child>>,
) -> crate::Result<ExitStatus> {
let current_child = current_child.clone();
// Wait on current Minecraft Child
let mut mc_exit_status;
loop {
if let Some(t) = current_child.write().await.try_wait()? {
mc_exit_status = t;
break;
}
}
if !mc_exit_status.success() {
return Ok(mc_exit_status); // Err for a non-zero exit is handled in helper
}
// If a post-command exist, switch to it and wait on it
if let Some(mut m_command) = post_command {
{
let mut current_child = current_child.write().await;
let new_child = m_command.spawn()?;
current_pid = new_child.id().ok_or_else(|| {
crate::ErrorKind::LauncherError(
"Process immediately failed, could not get PID"
.to_string(),
)
})?;
*current_child = new_child;
}
emit_process(
uuid,
current_pid,
ProcessPayloadType::Updated,
"Completed Minecraft, switching to post-commands",
)
.await?;
loop {
if let Some(t) = current_child.write().await.try_wait()? {
mc_exit_status = t;
break;
}
}
}
emit_process(
uuid,
current_pid,
ProcessPayloadType::Finished,
"Exited process",
)
.await?;
Ok(mc_exit_status)
}
// Returns a ref to the child
pub fn get(&self, pid: &u32) -> Option<Arc<RwLock<MinecraftChild>>> {
self.0.get(pid).cloned()
pub fn get(&self, uuid: &Uuid) -> Option<Arc<RwLock<MinecraftChild>>> {
self.0.get(uuid).cloned()
}
// Gets all PID keys
pub fn keys(&self) -> Vec<u32> {
pub fn keys(&self) -> Vec<Uuid> {
self.0.keys().cloned().collect()
}
@@ -96,25 +180,25 @@ impl Children {
// Returns None if the child is still running
pub async fn exit_status(
&self,
pid: &u32,
uuid: &Uuid,
) -> 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()?)
if let Some(child) = self.get(uuid) {
let child = child.write().await;
let status = child.current_child.write().await.try_wait()?;
Ok(status)
} else {
Ok(None)
}
}
// Gets all PID keys of running children
pub async fn running_keys(&self) -> crate::Result<Vec<u32>> {
pub async fn running_keys(&self) -> crate::Result<Vec<Uuid>> {
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() {
let child = child.write().await;
if child.current_child.write().await.try_wait()?.is_none() {
keys.push(key);
}
}
@@ -126,7 +210,7 @@ impl Children {
pub async fn running_keys_with_profile(
&self,
profile_path: &Path,
) -> crate::Result<Vec<u32>> {
) -> crate::Result<Vec<Uuid>> {
let running_keys = self.running_keys().await?;
let mut keys = Vec::new();
for key in running_keys {
@@ -147,8 +231,8 @@ impl Children {
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() {
let child = child.write().await;
if child.current_child.write().await.try_wait()?.is_none() {
profiles.push(child.profile_path.clone());
}
}
@@ -163,8 +247,8 @@ impl Children {
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() {
let child = child.write().await;
if child.current_child.write().await.try_wait()?.is_none() {
if let Some(prof) =
crate::api::profile::get(&child.profile_path.clone())
.await?

View File

@@ -1,6 +1,6 @@
//! Theseus settings file
use serde::{Deserialize, Serialize};
use std::{collections::HashSet, path::Path};
use std::path::Path;
use tokio::fs;
use super::JavaGlobals;
@@ -13,6 +13,7 @@ const CURRENT_FORMAT_VERSION: u32 = 1;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(default)]
pub struct Settings {
pub theme: Theme,
pub memory: MemorySettings,
pub game_resolution: WindowSize,
pub custom_java_args: Vec<String>,
@@ -27,6 +28,7 @@ pub struct Settings {
impl Default for Settings {
fn default() -> Self {
Self {
theme: Theme::Dark,
memory: MemorySettings::default(),
game_resolution: WindowSize::default(),
custom_java_args: Vec::new(),
@@ -74,6 +76,15 @@ impl Settings {
}
}
/// Theseus theme
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Theme {
Dark,
Light,
Oled,
}
/// Minecraft memory settings
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct MemorySettings {
@@ -105,10 +116,10 @@ impl Default for WindowSize {
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(default)]
pub struct Hooks {
#[serde(skip_serializing_if = "HashSet::is_empty")]
pub pre_launch: HashSet<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pre_launch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wrapper: Option<String>,
#[serde(skip_serializing_if = "HashSet::is_empty")]
pub post_exit: HashSet<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub post_exit: Option<String>,
}