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

79
Cargo.lock generated
View File

@@ -2110,6 +2110,17 @@ dependencies = [
"objc_exception", "objc_exception",
] ]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]] [[package]]
name = "objc_exception" name = "objc_exception"
version = "0.1.2" version = "0.1.2"
@@ -2733,6 +2744,30 @@ dependencies = [
"winreg 0.10.1", "winreg 0.10.1",
] ]
[[package]]
name = "rfd"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea"
dependencies = [
"block",
"dispatch",
"glib-sys",
"gobject-sys",
"gtk-sys",
"js-sys",
"lazy_static",
"log",
"objc",
"objc-foundation",
"objc_id",
"raw-window-handle",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows 0.37.0",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.22" version = "0.1.22"
@@ -3344,6 +3379,7 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
"raw-window-handle", "raw-window-handle",
"regex", "regex",
"rfd",
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
@@ -4304,6 +4340,19 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647"
dependencies = [
"windows_aarch64_msvc 0.37.0",
"windows_i686_gnu 0.37.0",
"windows_i686_msvc 0.37.0",
"windows_x86_64_gnu 0.37.0",
"windows_x86_64_msvc 0.37.0",
]
[[package]] [[package]]
name = "windows" name = "windows"
version = "0.39.0" version = "0.39.0"
@@ -4413,6 +4462,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.39.0" version = "0.39.0"
@@ -4425,6 +4480,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.39.0" version = "0.39.0"
@@ -4437,6 +4498,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.39.0" version = "0.39.0"
@@ -4449,6 +4516,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.39.0" version = "0.39.0"
@@ -4467,6 +4540,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.39.0" version = "0.39.0"

View File

@@ -1,4 +1,6 @@
//! Authentication flow interface //! Authentication flow interface
use std::path::PathBuf;
use crate::{ use crate::{
launcher::download, launcher::download,
prelude::Profile, prelude::Profile,
@@ -139,3 +141,8 @@ pub async fn validate_globals() -> crate::Result<bool> {
let settings = state.settings.read().await; let settings = state.settings.read().await;
Ok(settings.java_globals.is_all_valid()) Ok(settings.java_globals.is_all_valid())
} }
// Validates JRE at a given at a given path
pub async fn check_jre(path: PathBuf) -> crate::Result<Option<JavaVersion>> {
Ok(jre::check_java_at_filepath(&path))
}

View File

@@ -11,7 +11,7 @@ pub mod tags;
pub mod data { pub mod data {
pub use crate::state::{ pub use crate::state::{
DirectoryInfo, Hooks, JavaSettings, MemorySettings, ModLoader, DirectoryInfo, Hooks, JavaSettings, MemorySettings, ModLoader,
ProfileMetadata, Settings, WindowSize, ProfileMetadata, Settings, Theme, WindowSize,
}; };
} }

View File

@@ -230,7 +230,7 @@ async fn install_pack(
let loading_bar = init_loading( let loading_bar = init_loading(
LoadingBarType::PackDownload { LoadingBarType::PackDownload {
pack_name , pack_name,
pack_id: project_id, pack_id: project_id,
pack_version: version_id, pack_version: version_id,
}, },

View File

@@ -1,6 +1,8 @@
//! Theseus process management interface //! Theseus process management interface
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uuid::Uuid;
use crate::state::MinecraftChild; use crate::state::MinecraftChild;
pub use crate::{ pub use crate::{
state::{ state::{
@@ -9,31 +11,33 @@ pub use crate::{
State, State,
}; };
// Gets whether a child process stored in the state by PID has finished // Gets whether a child process stored in the state by UUID has finished
#[tracing::instrument] #[tracing::instrument]
pub async fn has_finished_by_pid(pid: u32) -> crate::Result<bool> { pub async fn has_finished_by_uuid(uuid: &Uuid) -> crate::Result<bool> {
Ok(get_exit_status_by_pid(pid).await?.is_some()) Ok(get_exit_status_by_uuid(uuid).await?.is_some())
} }
// Gets the exit status of a child process stored in the state by PID // Gets the exit status of a child process stored in the state by UUID
#[tracing::instrument] #[tracing::instrument]
pub async fn get_exit_status_by_pid(pid: u32) -> crate::Result<Option<i32>> { pub async fn get_exit_status_by_uuid(
uuid: &Uuid,
) -> crate::Result<Option<i32>> {
let state = State::get().await?; let state = State::get().await?;
let children = state.children.read().await; let children = state.children.read().await;
Ok(children.exit_status(&pid).await?.and_then(|f| f.code())) Ok(children.exit_status(uuid).await?.and_then(|f| f.code()))
} }
// Gets the PID of each stored process in the state // Gets the UUID of each stored process in the state
#[tracing::instrument] #[tracing::instrument]
pub async fn get_all_pids() -> crate::Result<Vec<u32>> { pub async fn get_all_uuids() -> crate::Result<Vec<Uuid>> {
let state = State::get().await?; let state = State::get().await?;
let children = state.children.read().await; let children = state.children.read().await;
Ok(children.keys()) Ok(children.keys())
} }
// Gets the PID of each *running* stored process in the state // Gets the UUID of each *running* stored process in the state
#[tracing::instrument] #[tracing::instrument]
pub async fn get_all_running_pids() -> crate::Result<Vec<u32>> { pub async fn get_all_running_uuids() -> crate::Result<Vec<Uuid>> {
let state = State::get().await?; let state = State::get().await?;
let children = state.children.read().await; let children = state.children.read().await;
children.running_keys().await children.running_keys().await
@@ -55,62 +59,62 @@ pub async fn get_all_running_profiles() -> crate::Result<Vec<Profile>> {
children.running_profiles().await children.running_profiles().await
} }
// Gets the PID of each stored process in the state by profile path // Gets the UUID of each stored process in the state by profile path
#[tracing::instrument] #[tracing::instrument]
pub async fn get_pids_by_profile_path( pub async fn get_uuids_by_profile_path(
profile_path: &Path, profile_path: &Path,
) -> crate::Result<Vec<u32>> { ) -> crate::Result<Vec<Uuid>> {
let state = State::get().await?; let state = State::get().await?;
let children = state.children.read().await; let children = state.children.read().await;
children.running_keys_with_profile(profile_path).await children.running_keys_with_profile(profile_path).await
} }
// Gets stdout of a child process stored in the state by PID, as a string // Gets stdout of a child process stored in the state by UUID, as a string
#[tracing::instrument] #[tracing::instrument]
pub async fn get_stdout_by_pid(pid: u32) -> crate::Result<String> { pub async fn get_stdout_by_uuid(uuid: &Uuid) -> crate::Result<String> {
let state = State::get().await?; let state = State::get().await?;
// Get stdout from child // Get stdout from child
let children = state.children.read().await; let children = state.children.read().await;
// Extract child or return crate::Error // Extract child or return crate::Error
if let Some(child) = children.get(&pid) { if let Some(child) = children.get(uuid) {
let child = child.read().await; let child = child.read().await;
Ok(child.stdout.get_output().await?) Ok(child.stdout.get_output().await?)
} else { } else {
Err(crate::ErrorKind::LauncherError(format!( Err(crate::ErrorKind::LauncherError(format!(
"No child process with PID {}", "No child process by UUID {}",
pid uuid
)) ))
.as_error()) .as_error())
} }
} }
// Gets stderr of a child process stored in the state by PID, as a string // Gets stderr of a child process stored in the state by UUID, as a string
#[tracing::instrument] #[tracing::instrument]
pub async fn get_stderr_by_pid(pid: u32) -> crate::Result<String> { pub async fn get_stderr_by_uuid(uuid: &Uuid) -> crate::Result<String> {
let state = State::get().await?; let state = State::get().await?;
// Get stdout from child // Get stdout from child
let children = state.children.read().await; let children = state.children.read().await;
// Extract child or return crate::Error // Extract child or return crate::Error
if let Some(child) = children.get(&pid) { if let Some(child) = children.get(uuid) {
let child = child.read().await; let child = child.read().await;
Ok(child.stderr.get_output().await?) Ok(child.stderr.get_output().await?)
} else { } else {
Err(crate::ErrorKind::LauncherError(format!( Err(crate::ErrorKind::LauncherError(format!(
"No child process with PID {}", "No child process with UUID {}",
pid uuid
)) ))
.as_error()) .as_error())
} }
} }
// Kill a child process stored in the state by PID, as a string // Kill a child process stored in the state by UUID, as a string
#[tracing::instrument] #[tracing::instrument]
pub async fn kill_by_pid(pid: u32) -> crate::Result<()> { pub async fn kill_by_uuid(uuid: &Uuid) -> crate::Result<()> {
let state = State::get().await?; let state = State::get().await?;
let children = state.children.read().await; let children = state.children.read().await;
if let Some(mchild) = children.get(&pid) { if let Some(mchild) = children.get(uuid) {
let mut mchild = mchild.write().await; let mut mchild = mchild.write().await;
kill(&mut mchild).await kill(&mut mchild).await
} else { } else {
@@ -119,13 +123,13 @@ pub async fn kill_by_pid(pid: u32) -> crate::Result<()> {
} }
} }
// Wait for a child process stored in the state by PID // Wait for a child process stored in the state by UUID
#[tracing::instrument] #[tracing::instrument]
pub async fn wait_for_by_pid(pid: u32) -> crate::Result<()> { pub async fn wait_for_by_uuid(uuid: &Uuid) -> crate::Result<()> {
let state = State::get().await?; let state = State::get().await?;
let children = state.children.read().await; let children = state.children.read().await;
// No error returned for already killed process // No error returned for already killed process
if let Some(mchild) = children.get(&pid) { if let Some(mchild) = children.get(uuid) {
let mut mchild = mchild.write().await; let mut mchild = mchild.write().await;
wait_for(&mut mchild).await wait_for(&mut mchild).await
} else { } else {
@@ -137,18 +141,30 @@ pub async fn wait_for_by_pid(pid: u32) -> crate::Result<()> {
// Kill a running child process directly, and wait for it to be killed // Kill a running child process directly, and wait for it to be killed
#[tracing::instrument] #[tracing::instrument]
pub async fn kill(running: &mut MinecraftChild) -> crate::Result<()> { pub async fn kill(running: &mut MinecraftChild) -> crate::Result<()> {
running.child.kill().await?; running.current_child.write().await.kill().await?;
wait_for(running).await wait_for(running).await
} }
// Await on the completion of a child process directly // Await on the completion of a child process directly
#[tracing::instrument] #[tracing::instrument]
pub async fn wait_for(running: &mut MinecraftChild) -> crate::Result<()> { pub async fn wait_for(running: &mut MinecraftChild) -> crate::Result<()> {
let result = running.child.wait().await.map_err(|err| { // We do not wait on the Child directly, but wait on the thread manager.
crate::ErrorKind::LauncherError(format!( // This way we can still run all cleanup hook functions that happen after.
"Error running minecraft: {err}" let result = running
)) .manager
})?; .take()
.ok_or_else(|| {
crate::ErrorKind::LauncherError(format!(
"Process manager already completed or missing for process {}",
running.uuid
))
})?
.await?
.map_err(|err| {
crate::ErrorKind::LauncherError(format!(
"Error running minecraft: {err}"
))
})?;
match result.success() { match result.success() {
false => Err(crate::ErrorKind::LauncherError(format!( false => Err(crate::ErrorKind::LauncherError(format!(

View File

@@ -256,7 +256,7 @@ pub async fn run_credentials(
.await?; .await?;
let pre_launch_hooks = let pre_launch_hooks =
&profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch; &profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch;
for hook in pre_launch_hooks.iter() { if let Some(hook) = pre_launch_hooks {
// TODO: hook parameters // TODO: hook parameters
let mut cmd = hook.split(' '); let mut cmd = hook.split(' ');
if let Some(command) = cmd.next() { if let Some(command) = cmd.next() {
@@ -336,6 +336,23 @@ pub async fn run_credentials(
let env_args = &settings.custom_env_args; let env_args = &settings.custom_env_args;
// Post post exit hooks
let post_exit_hook =
&profile.hooks.as_ref().unwrap_or(&settings.hooks).post_exit;
let post_exit_hook = if let Some(hook) = post_exit_hook {
let mut cmd = hook.split(' ');
if let Some(command) = cmd.next() {
let mut command = Command::new(command);
command.args(&cmd.collect::<Vec<&str>>()).current_dir(path);
Some(command)
} else {
None
}
} else {
None
};
let mc_process = crate::launcher::launch_minecraft( let mc_process = crate::launcher::launch_minecraft(
&profile.metadata.game_version, &profile.metadata.game_version,
&profile.metadata.loader_version, &profile.metadata.loader_version,
@@ -347,20 +364,10 @@ pub async fn run_credentials(
&memory, &memory,
&resolution, &resolution,
credentials, credentials,
post_exit_hook,
&profile, &profile,
) )
.await?; .await?;
// Insert child into state Ok(mc_process)
let mut state_children = state.children.write().await;
let pid = mc_process.id().ok_or_else(|| {
crate::ErrorKind::LauncherError(
"Process failed to stay open.".to_string(),
)
})?;
let mchild_arc = state_children
.insert_process(pid, path.to_path_buf(), mc_process)
.await?;
Ok(mchild_arc)
} }

View File

@@ -129,7 +129,7 @@ pub async fn emit_warning(message: &str) -> crate::Result<()> {
Ok(()) Ok(())
} }
// emit_process(pid, event, message) // emit_process(uuid, pid, event, message)
#[allow(unused_variables)] #[allow(unused_variables)]
pub async fn emit_process( pub async fn emit_process(
uuid: uuid::Uuid, uuid: uuid::Uuid,

View File

@@ -121,11 +121,11 @@ pub struct ProcessPayload {
pub event: ProcessPayloadType, pub event: ProcessPayloadType,
pub message: String, pub message: String,
} }
#[derive(Serialize, Clone)] #[derive(Serialize, Clone, Debug)]
pub enum ProcessPayloadType { pub enum ProcessPayloadType {
Launched, Launched,
// Finishing, // TODO: process restructing incoming, currently this is never emitted Updated, // eg: if the MinecraftChild changes to its post-command process instead of the Minecraft process
// Finished, // TODO: process restructing incoming, currently this is never emitted Finished,
} }
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]

View File

@@ -33,7 +33,7 @@ pub async fn download_minecraft(
LoadingBarType::MinecraftDownload { LoadingBarType::MinecraftDownload {
// If we are downloading minecraft for a profile, provide its name and uuid // If we are downloading minecraft for a profile, provide its name and uuid
profile_name: profile.metadata.name.clone(), profile_name: profile.metadata.name.clone(),
profile_uuid: profile.uuid, profile_uuid: profile.uuid,
}, },
100.0, 100.0,
"Downloading Minecraft...", "Downloading Minecraft...",

View File

@@ -1,10 +1,13 @@
//! Logic for launching Minecraft //! Logic for launching Minecraft
use crate::{process, state as st}; use crate::{
process,
state::{self as st, MinecraftChild},
};
use daedalus as d; use daedalus as d;
use dunce::canonicalize; use dunce::canonicalize;
use st::Profile; use st::Profile;
use std::{path::Path, process::Stdio}; use std::{path::Path, process::Stdio, sync::Arc};
use tokio::process::{Child, Command}; use tokio::process::Command;
mod args; mod args;
@@ -58,8 +61,9 @@ pub async fn launch_minecraft(
memory: &st::MemorySettings, memory: &st::MemorySettings,
resolution: &st::WindowSize, resolution: &st::WindowSize,
credentials: &auth::Credentials, credentials: &auth::Credentials,
post_exit_hook: Option<Command>,
profile: &Profile, // optional ref to Profile for event tracking profile: &Profile, // optional ref to Profile for event tracking
) -> crate::Result<Child> { ) -> crate::Result<Arc<tokio::sync::RwLock<MinecraftChild>>> {
let state = st::State::get().await?; let state = st::State::get().await?;
let instance_path = &canonicalize(instance_path)?; let instance_path = &canonicalize(instance_path)?;
@@ -182,10 +186,10 @@ pub async fn launch_minecraft(
// Check if profile has a running profile, and reject running the command if it does // 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 // Done late so a quick double call doesn't launch two instances
let existing_processes = let existing_processes =
process::get_pids_by_profile_path(instance_path).await?; process::get_uuids_by_profile_path(instance_path).await?;
if let Some(pid) = existing_processes.first() { if let Some(uuid) = existing_processes.first() {
return Err(crate::ErrorKind::LauncherError(format!( return Err(crate::ErrorKind::LauncherError(format!(
"Profile {} is already running at PID: {pid}", "Profile {} is already running at UUID: {uuid}",
instance_path.display() instance_path.display()
)) ))
.as_error()); .as_error());
@@ -233,12 +237,15 @@ pub async fn launch_minecraft(
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()); .stderr(Stdio::piped());
command.spawn().map_err(|err| { // Create Minecraft child by inserting it into the state
crate::ErrorKind::LauncherError(format!( // This also spawns the process and prepares the subsequent processes
"Error running Minecraft (minecraft-{} @ {}): {err}", let mut state_children = state.children.write().await;
&version.id, state_children
instance_path.display() .insert_process(
)) uuid::Uuid::new_v4(),
.as_error() instance_path.to_path_buf(),
}) command,
post_exit_hook,
)
.await
} }

View File

@@ -1,25 +1,29 @@
use super::Profile;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::ExitStatus;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Child;
use tokio::process::Command;
use tokio::process::{ChildStderr, ChildStdout}; use tokio::process::{ChildStderr, ChildStdout};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use crate::event::emit::emit_process; use crate::event::emit::emit_process;
use crate::event::ProcessPayloadType; use crate::event::ProcessPayloadType;
use tokio::task::JoinHandle;
use super::Profile; use uuid::Uuid;
// Child processes (instances of Minecraft) // Child processes (instances of Minecraft)
// A wrapper over a Hashmap connecting PID -> MinecraftChild // 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 // Minecraft Child, bundles together the PID, the actual Child, and the easily queryable stdout and stderr streams
#[derive(Debug)] #[derive(Debug)]
pub struct MinecraftChild { pub struct MinecraftChild {
pub uuid: uuid::Uuid, pub uuid: Uuid,
pub pid: u32,
pub profile_path: PathBuf, //todo: make UUID when profiles are recognized by 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 stdout: SharedOutput,
pub stderr: SharedOutput, pub stderr: SharedOutput,
} }
@@ -29,16 +33,18 @@ impl Children {
Children(HashMap::new()) 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 // 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( pub async fn insert_process(
&mut self, &mut self,
pid: u32, uuid: Uuid,
profile_path: PathBuf, 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>>> { ) -> 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 // Create std watcher threads for stdout and stderr
let stdout = SharedOutput::new(); let stdout = SharedOutput::new();
@@ -55,11 +61,25 @@ impl Children {
let stderr_clone = stderr.clone(); let stderr_clone = stderr.clone();
tokio::spawn(async move { tokio::spawn(async move {
if let Err(e) = stderr_clone.read_stderr(child_stderr).await { 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( emit_process(
uuid, uuid,
pid, pid,
@@ -71,24 +91,88 @@ impl Children {
// Create MinecraftChild // Create MinecraftChild
let mchild = MinecraftChild { let mchild = MinecraftChild {
uuid, uuid,
pid,
profile_path, profile_path,
child, current_child,
stdout, stdout,
stderr, stderr,
manager,
}; };
let mchild = Arc::new(RwLock::new(mchild)); let mchild = Arc::new(RwLock::new(mchild));
self.0.insert(pid, mchild.clone()); self.0.insert(uuid, mchild.clone());
Ok(mchild) 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 // Returns a ref to the child
pub fn get(&self, pid: &u32) -> Option<Arc<RwLock<MinecraftChild>>> { pub fn get(&self, uuid: &Uuid) -> Option<Arc<RwLock<MinecraftChild>>> {
self.0.get(pid).cloned() self.0.get(uuid).cloned()
} }
// Gets all PID keys // Gets all PID keys
pub fn keys(&self) -> Vec<u32> { pub fn keys(&self) -> Vec<Uuid> {
self.0.keys().cloned().collect() self.0.keys().cloned().collect()
} }
@@ -96,25 +180,25 @@ impl Children {
// Returns None if the child is still running // Returns None if the child is still running
pub async fn exit_status( pub async fn exit_status(
&self, &self,
pid: &u32, uuid: &Uuid,
) -> crate::Result<Option<std::process::ExitStatus>> { ) -> crate::Result<Option<std::process::ExitStatus>> {
if let Some(child) = self.get(pid) { if let Some(child) = self.get(uuid) {
let child = child.clone(); let child = child.write().await;
let mut child = child.write().await; let status = child.current_child.write().await.try_wait()?;
Ok(child.child.try_wait()?) Ok(status)
} else { } else {
Ok(None) Ok(None)
} }
} }
// Gets all PID keys of running children // 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(); let mut keys = Vec::new();
for key in self.keys() { for key in self.keys() {
if let Some(child) = self.get(&key) { if let Some(child) = self.get(&key) {
let child = child.clone(); let child = child.clone();
let mut child = child.write().await; let child = child.write().await;
if child.child.try_wait()?.is_none() { if child.current_child.write().await.try_wait()?.is_none() {
keys.push(key); keys.push(key);
} }
} }
@@ -126,7 +210,7 @@ impl Children {
pub async fn running_keys_with_profile( pub async fn running_keys_with_profile(
&self, &self,
profile_path: &Path, profile_path: &Path,
) -> crate::Result<Vec<u32>> { ) -> crate::Result<Vec<Uuid>> {
let running_keys = self.running_keys().await?; let running_keys = self.running_keys().await?;
let mut keys = Vec::new(); let mut keys = Vec::new();
for key in running_keys { for key in running_keys {
@@ -147,8 +231,8 @@ impl Children {
for key in self.keys() { for key in self.keys() {
if let Some(child) = self.get(&key) { if let Some(child) = self.get(&key) {
let child = child.clone(); let child = child.clone();
let mut child = child.write().await; let child = child.write().await;
if child.child.try_wait()?.is_none() { if child.current_child.write().await.try_wait()?.is_none() {
profiles.push(child.profile_path.clone()); profiles.push(child.profile_path.clone());
} }
} }
@@ -163,8 +247,8 @@ impl Children {
for key in self.keys() { for key in self.keys() {
if let Some(child) = self.get(&key) { if let Some(child) = self.get(&key) {
let child = child.clone(); let child = child.clone();
let mut child = child.write().await; let child = child.write().await;
if child.child.try_wait()?.is_none() { if child.current_child.write().await.try_wait()?.is_none() {
if let Some(prof) = if let Some(prof) =
crate::api::profile::get(&child.profile_path.clone()) crate::api::profile::get(&child.profile_path.clone())
.await? .await?

View File

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

View File

@@ -19,7 +19,7 @@ theseus = { path = "../../theseus", features = ["tauri"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.2", features = ["protocol-asset", "window-close", "window-create"] } tauri = { version = "1.2", features = ["protocol-asset", "window-close", "window-create", "dialog"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
thiserror = "1.0" thiserror = "1.0"
tokio-stream = { version = "0.1", features = ["fs"] } tokio-stream = { version = "0.1", features = ["fs"] }

View File

@@ -1,4 +1,4 @@
use std::path::Path; use std::path::{Path, PathBuf};
use crate::api::Result; use crate::api::Result;
use theseus::prelude::JavaVersion; use theseus::prelude::JavaVersion;
@@ -60,3 +60,10 @@ pub async fn jre_get_optimal_jre_key_by_path(path: &Path) -> Result<String> {
pub async fn jre_validate_globals() -> Result<bool> { pub async fn jre_validate_globals() -> Result<bool> {
Ok(jre::validate_globals().await?) Ok(jre::validate_globals().await?)
} }
// Validates JRE at a given path
// Returns None if the path is not a valid JRE
#[tauri::command]
pub async fn jre_get_jre(path: PathBuf) -> Result<Option<JavaVersion>> {
jre::check_jre(path).await.map_err(|e| e.into())
}

View File

@@ -33,6 +33,9 @@ pub enum TheseusSerializableError {
#[error("No profile found at {0}")] #[error("No profile found at {0}")]
NoProfileFound(String), NoProfileFound(String),
#[error("Improperly formatted environment variables: {0}")]
BadEnvVars(String),
} }
// Generic implementation of From<T> for ErrorTypeA // Generic implementation of From<T> for ErrorTypeA
@@ -74,4 +77,5 @@ impl_serialize! {
Theseus, Theseus,
IO, IO,
NoProfileFound, NoProfileFound,
BadEnvVars,
} }

View File

@@ -2,37 +2,40 @@ use std::path::{Path, PathBuf};
use crate::api::Result; use crate::api::Result;
use theseus::prelude::*; use theseus::prelude::*;
use uuid::Uuid;
// Checks if a process has finished by process PID // Checks if a process has finished by process UUID
#[tauri::command] #[tauri::command]
pub async fn process_has_finished_by_pid(pid: u32) -> Result<bool> { pub async fn process_has_finished_by_uuid(uuid: Uuid) -> Result<bool> {
Ok(process::has_finished_by_pid(pid).await?) Ok(process::has_finished_by_uuid(&uuid).await?)
} }
// Gets process exit status by process PID // Gets process exit status by process UUID
#[tauri::command] #[tauri::command]
pub async fn process_get_exit_status_by_pid(pid: u32) -> Result<Option<i32>> { pub async fn process_get_exit_status_by_uuid(
Ok(process::get_exit_status_by_pid(pid).await?) uuid: Uuid,
) -> Result<Option<i32>> {
Ok(process::get_exit_status_by_uuid(&uuid).await?)
} }
// Gets all process PIDs // Gets all process UUIDs
#[tauri::command] #[tauri::command]
pub async fn process_get_all_pids() -> Result<Vec<u32>> { pub async fn process_get_all_uuids() -> Result<Vec<Uuid>> {
Ok(process::get_all_pids().await?) Ok(process::get_all_uuids().await?)
} }
// Gets all running process PIDs // Gets all running process UUIDs
#[tauri::command] #[tauri::command]
pub async fn process_get_all_running_pids() -> Result<Vec<u32>> { pub async fn process_get_all_running_uuids() -> Result<Vec<Uuid>> {
Ok(process::get_all_running_pids().await?) Ok(process::get_all_running_uuids().await?)
} }
// Gets all process PIDs by profile path // Gets all process UUIDs by profile path
#[tauri::command] #[tauri::command]
pub async fn process_get_pids_by_profile_path( pub async fn process_get_uuids_by_profile_path(
profile_path: &Path, profile_path: &Path,
) -> Result<Vec<u32>> { ) -> Result<Vec<Uuid>> {
Ok(process::get_pids_by_profile_path(profile_path).await?) Ok(process::get_uuids_by_profile_path(profile_path).await?)
} }
// Gets the Profile paths of each *running* stored process in the state // Gets the Profile paths of each *running* stored process in the state
@@ -47,26 +50,26 @@ pub async fn process_get_all_running_profiles() -> Result<Vec<Profile>> {
Ok(process::get_all_running_profiles().await?) Ok(process::get_all_running_profiles().await?)
} }
// Gets process stderr by process PID // Gets process stderr by process UUID
#[tauri::command] #[tauri::command]
pub async fn process_get_stderr_by_pid(pid: u32) -> Result<String> { pub async fn process_get_stderr_by_uuid(uuid: Uuid) -> Result<String> {
Ok(process::get_stderr_by_pid(pid).await?) Ok(process::get_stderr_by_uuid(&uuid).await?)
} }
// Gets process stdout by process PID // Gets process stdout by process UUID
#[tauri::command] #[tauri::command]
pub async fn process_get_stdout_by_pid(pid: u32) -> Result<String> { pub async fn process_get_stdout_by_uuid(uuid: Uuid) -> Result<String> {
Ok(process::get_stdout_by_pid(pid).await?) Ok(process::get_stdout_by_uuid(&uuid).await?)
} }
// Kill a process by process PID // Kill a process by process UUID
#[tauri::command] #[tauri::command]
pub async fn process_kill_by_pid(pid: u32) -> Result<()> { pub async fn process_kill_by_uuid(uuid: Uuid) -> Result<()> {
Ok(process::kill_by_pid(pid).await?) Ok(process::kill_by_uuid(&uuid).await?)
} }
// Wait for a process to finish by process PID // Wait for a process to finish by process UUID
#[tauri::command] #[tauri::command]
pub async fn process_wait_for_by_pid(pid: u32) -> Result<()> { pub async fn process_wait_for_by_uuid(uuid: Uuid) -> Result<()> {
Ok(process::wait_for_by_pid(pid).await?) Ok(process::wait_for_by_uuid(&uuid).await?)
} }

View File

@@ -1,6 +1,7 @@
use crate::api::Result; use crate::api::Result;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use theseus::prelude::*; use theseus::prelude::*;
use uuid::Uuid;
// Remove a profile // Remove a profile
// invoke('profile_add_path',path) // invoke('profile_add_path',path)
@@ -73,18 +74,14 @@ pub async fn profile_remove_project(
Ok(()) Ok(())
} }
// Run minecraft using a profile using the default credentials // Run minecraft using a profile using the default credentials
// Returns a u32 representing the PID, which can be used to poll // Returns the UUID, which can be used to poll
// for the actual Child in the state. // for the actual Child in the state.
// invoke('profile_run', path) // invoke('profile_run', path)
#[tauri::command] #[tauri::command]
pub async fn profile_run(path: &Path) -> Result<u32> { pub async fn profile_run(path: &Path) -> Result<Uuid> {
let proc_lock = profile::run(path).await?; let minecraft_child = profile::run(path).await?;
let pid = proc_lock.read().await.child.id().ok_or_else(|| { let uuid = minecraft_child.read().await.uuid;
theseus::Error::from(theseus::ErrorKind::LauncherError( Ok(uuid)
"Process failed to stay open.".to_string(),
))
})?;
Ok(pid)
} }
// Run Minecraft using a profile using the default credentials, and wait for the result // Run Minecraft using a profile using the default credentials, and wait for the result
@@ -97,21 +94,17 @@ pub async fn profile_run_wait(path: &Path) -> Result<()> {
} }
// Run Minecraft using a profile using chosen credentials // Run Minecraft using a profile using chosen credentials
// Returns a u32 representing the PID, which can be used to poll // Returns the UUID, which can be used to poll
// for the actual Child in the state. // for the actual Child in the state.
// invoke('profile_run_credentials', {path, credentials})') // invoke('profile_run_credentials', {path, credentials})')
#[tauri::command] #[tauri::command]
pub async fn profile_run_credentials( pub async fn profile_run_credentials(
path: &Path, path: &Path,
credentials: Credentials, credentials: Credentials,
) -> Result<u32> { ) -> Result<Uuid> {
let proc_lock = profile::run_credentials(path, &credentials).await?; let minecraft_child = profile::run_credentials(path, &credentials).await?;
let pid = proc_lock.read().await.child.id().ok_or_else(|| { let uuid = minecraft_child.read().await.uuid;
theseus::Error::from(theseus::ErrorKind::LauncherError( Ok(uuid)
"Process failed to stay open.".to_string(),
))
})?;
Ok(pid)
} }
// Run Minecraft using a profile using the chosen credentials, and wait for the result // Run Minecraft using a profile using the chosen credentials, and wait for the result

View File

@@ -2,14 +2,17 @@ use crate::api::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use theseus::prelude::*; use theseus::prelude::*;
use super::TheseusSerializableError;
// Identical to theseus::settings::Settings except for the custom_java_args field // Identical to theseus::settings::Settings except for the custom_java_args field
// This allows us to split the custom_java_args string into a Vec<String> here and join it back into a string in the backend // This allows us to split the custom_java_args string into a Vec<String> here and join it back into a string in the backend
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FrontendSettings { pub struct FrontendSettings {
pub theme: Theme,
pub memory: MemorySettings, pub memory: MemorySettings,
pub game_resolution: WindowSize, pub game_resolution: WindowSize,
pub custom_java_args: String, pub custom_java_args: String,
pub custom_env_args: Vec<(String, String)>, pub custom_env_args: String,
pub java_globals: JavaGlobals, pub java_globals: JavaGlobals,
pub default_user: Option<uuid::Uuid>, pub default_user: Option<uuid::Uuid>,
pub hooks: Hooks, pub hooks: Hooks,
@@ -23,10 +26,16 @@ pub struct FrontendSettings {
pub async fn settings_get() -> Result<FrontendSettings> { pub async fn settings_get() -> Result<FrontendSettings> {
let backend_settings = settings::get().await?; let backend_settings = settings::get().await?;
let frontend_settings = FrontendSettings { let frontend_settings = FrontendSettings {
theme: backend_settings.theme,
memory: backend_settings.memory, memory: backend_settings.memory,
game_resolution: backend_settings.game_resolution, game_resolution: backend_settings.game_resolution,
custom_java_args: backend_settings.custom_java_args.join(" "), custom_java_args: backend_settings.custom_java_args.join(" "),
custom_env_args: backend_settings.custom_env_args, custom_env_args: backend_settings
.custom_env_args
.into_iter()
.map(|(s1, s2)| format!("{s1}={s2}"))
.collect::<Vec<String>>()
.join(" "),
java_globals: backend_settings.java_globals, java_globals: backend_settings.java_globals,
default_user: backend_settings.default_user, default_user: backend_settings.default_user,
hooks: backend_settings.hooks, hooks: backend_settings.hooks,
@@ -40,7 +49,25 @@ pub async fn settings_get() -> Result<FrontendSettings> {
// invoke('settings_set', settings) // invoke('settings_set', settings)
#[tauri::command] #[tauri::command]
pub async fn settings_set(settings: FrontendSettings) -> Result<()> { pub async fn settings_set(settings: FrontendSettings) -> Result<()> {
let custom_env_args: Vec<(String, String)> = settings
.custom_env_args
.split_whitespace()
.map(|s| s.to_string())
.map(|f| {
let mut split = f.split('=');
if let (Some(name), Some(value)) = (split.next(), split.next()) {
Ok((name.to_string(), value.to_string()))
} else {
Err(TheseusSerializableError::BadEnvVars(
"Invalid environment variable: {}".to_string(),
)
.into())
}
})
.collect::<Result<Vec<(String, String)>>>()?;
let backend_settings = Settings { let backend_settings = Settings {
theme: settings.theme,
memory: settings.memory, memory: settings.memory,
game_resolution: settings.game_resolution, game_resolution: settings.game_resolution,
custom_java_args: settings custom_java_args: settings
@@ -48,7 +75,7 @@ pub async fn settings_set(settings: FrontendSettings) -> Result<()> {
.split_whitespace() .split_whitespace()
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect(), .collect(),
custom_env_args: settings.custom_env_args, custom_env_args,
java_globals: settings.java_globals, java_globals: settings.java_globals,
default_user: settings.default_user, default_user: settings.default_user,
hooks: settings.hooks, hooks: settings.hooks,

View File

@@ -58,17 +58,18 @@ fn main() {
api::jre::jre_validate_globals, api::jre::jre_validate_globals,
api::jre::jre_get_optimal_jre_key, api::jre::jre_get_optimal_jre_key,
api::jre::jre_get_optimal_jre_key_by_path, api::jre::jre_get_optimal_jre_key_by_path,
api::process::process_get_all_pids, api::jre::jre_get_jre,
api::process::process_get_all_running_pids, api::process::process_get_all_uuids,
api::process::process_get_pids_by_profile_path, api::process::process_get_all_running_uuids,
api::process::process_get_uuids_by_profile_path,
api::process::process_get_all_running_profile_paths, api::process::process_get_all_running_profile_paths,
api::process::process_get_all_running_profiles, api::process::process_get_all_running_profiles,
api::process::process_get_exit_status_by_pid, api::process::process_get_exit_status_by_uuid,
api::process::process_has_finished_by_pid, api::process::process_has_finished_by_uuid,
api::process::process_get_stderr_by_pid, api::process::process_get_stderr_by_uuid,
api::process::process_get_stdout_by_pid, api::process::process_get_stdout_by_uuid,
api::process::process_kill_by_pid, api::process::process_kill_by_uuid,
api::process::process_wait_for_by_pid, api::process::process_wait_for_by_uuid,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@@ -44,6 +44,12 @@ export async function validate_globals() {
return await invoke('jre_validate_globals') return await invoke('jre_validate_globals')
} }
// Gets java version from a specific path by trying to run 'java -version' on it.
// This also validates it, as it returns null if no valid java version is found at the path
export async function get_jre(path) {
return await invoke('jre_get_jre', { path })
}
// Gets key for the optimal JRE to use, for a given profile path // Gets key for the optimal JRE to use, for a given profile path
// The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists) // The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists)
export async function get_optimal_jre_key_by_path(path) { export async function get_optimal_jre_key_by_path(path) {
@@ -52,7 +58,7 @@ export async function get_optimal_jre_key_by_path(path) {
// Gets key for the optimal JRE to use, for a given profile // Gets key for the optimal JRE to use, for a given profile
// The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists) // The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists)
export async function get_optimal_jre_ke(path) { export async function get_optimal_jre_key(path) {
return await invoke('jre_get_optimal_jre_key', { path }) return await invoke('jre_get_optimal_jre_key', { path })
} }

View File

@@ -5,34 +5,34 @@
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/tauri'
/// Gets if a process has finished by PID /// Gets if a process has finished by UUID
/// Returns bool /// Returns bool
export async function has_finished_by_pid(pid) { export async function has_finished_by_uuid(uuid) {
return await invoke('process_has_finished_by_pid', { pid }) return await invoke('process_has_finished_by_uuid', { uuid })
} }
/// Gets process exit status by PID /// Gets process exit status by UUID
/// Returns u32 /// Returns u32
export async function get_exit_status_by_pid(pid) { export async function get_exit_status_by_uuid(uuid) {
return await invoke('process_get_exit_status_by_pid', { pid }) return await invoke('process_get_exit_status_by_uuid', { uuid })
} }
/// Gets all process IDs /// Gets all process IDs
/// Returns [u32] /// Returns [u32]
export async function get_all_pids() { export async function get_all_uuids() {
return await invoke('process_get_all_pids') return await invoke('process_get_all_uuids')
} }
/// Gets all running process IDs /// Gets all running process IDs
/// Returns [u32] /// Returns [u32]
export async function get_all_running_pids() { export async function get_all_running_uuids() {
return await invoke('process_get_all_running_pids') return await invoke('process_get_all_running_uuids')
} }
/// Gets all running process IDs with a given profile path /// Gets all running process IDs with a given profile path
/// Returns [u32] /// Returns [u32]
export async function get_pids_by_profile_path(profile_path) { export async function get_uuids_by_profile_path(profile_path) {
return await invoke('process_get_pids_by_profile_path', { profile_path }) return await invoke('process_get_uuids_by_profile_path', { profile_path })
} }
/// Gets all running process IDs with a given profile path /// Gets all running process IDs with a given profile path
@@ -47,19 +47,19 @@ export async function get_all_running_profiles(profile_path) {
return await invoke('process_get_all_running_profiles', { profile_path }) return await invoke('process_get_all_running_profiles', { profile_path })
} }
/// Gets process stderr by PID /// Gets process stderr by UUID
/// Returns String /// Returns String
export async function get_stderr_by_pid(pid) { export async function get_stderr_by_uuid(uuid) {
return await invoke('process_get_stderr_by_pid', { pid }) return await invoke('process_get_stderr_by_uuid', { uuid })
} }
/// Gets process stdout by PID /// Gets process stdout by UUID
/// Returns String /// Returns String
export async function get_stdout_by_pid(pid) { export async function get_stdout_by_uuid(uuid) {
return await invoke('process_get_stdout_by_pid', { pid }) return await invoke('process_get_stdout_by_uuid', { uuid })
} }
/// Kills a process by PID /// Kills a process by UUID
export async function kill_by_pid(pid) { export async function kill_by_uuid(uuid) {
return await invoke('process_kill_by_pid', { pid }) return await invoke('process_kill_by_uuid', { uuid })
} }

View File

@@ -6,12 +6,21 @@
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/tauri'
// Add empty default instance // Add empty default instance
export async function addDefaultInstance() { export async function create_empty() {
return await invoke('profile_create_empty') return await invoke('profile_create_empty')
} }
/// Creates instance /// Add instance
/// Returns a path to the profile created /*
name: String, // the name of the profile, and relative path to create
game_version: String, // the game version of the profile
modloader: ModLoader, // the modloader to use
- ModLoader is an enum, with the following variants: Vanilla, Forge, Fabric, Quilt
loader_version: String, // the modloader version to use, set to "latest", "stable", or the ID of your chosen loader
icon: Path, // the icon for the profile
- icon is a path to an image file, which will be copied into the profile directory
*/
export async function create(name, game_version, modloader, loader_version, icon) { export async function create(name, game_version, modloader, loader_version, icon) {
return await invoke('profile_create', { name, game_version, modloader, loader_version, icon }) return await invoke('profile_create', { name, game_version, modloader, loader_version, icon })
} }

View File

@@ -33,6 +33,8 @@ async fn main() -> theseus::Result<()> {
// Initialize state // Initialize state
let st = State::get().await?; let st = State::get().await?;
st.settings.write().await.max_concurrent_downloads = 5; st.settings.write().await.max_concurrent_downloads = 5;
st.settings.write().await.hooks.post_exit =
Some("echo This is after Minecraft runs- global setting!".to_string());
// Changed the settings, so need to reset the semaphore // Changed the settings, so need to reset the semaphore
st.reset_semaphore().await; st.reset_semaphore().await;
@@ -88,11 +90,11 @@ async fn main() -> theseus::Result<()> {
// (ie: changing the java runtime of an added profile) // (ie: changing the java runtime of an added profile)
println!("Editing."); println!("Editing.");
profile::edit(&profile_path, |_profile| { profile::edit(&profile_path, |_profile| {
// Eg: Java- this would let you change the java runtime of the profile instead of using the default // Add some hooks, for instance!
// use theseus::prelude::jre::JAVA__KEY; // profile.hooks = Some(Hooks {
// profile.java = Some(JavaSettings { // pre_launch: Some("echo This is before Minecraft runs!".to_string()),
// jre_key: Some(JAVA_17_KEY.to_string()), // wrapper: None,
// extra_arguments: None, // post_exit: None,
// }); // });
async { Ok(()) } async { Ok(()) }
}) })
@@ -108,22 +110,22 @@ async fn main() -> theseus::Result<()> {
println!("running"); println!("running");
// Run a profile, running minecraft and store the RwLock to the process // Run a profile, running minecraft and store the RwLock to the process
let proc_lock = profile::run(&canonicalize(&profile_path)?).await?; let proc_lock = profile::run(&canonicalize(&profile_path)?).await?;
let uuid = proc_lock.read().await.uuid;
let pid = proc_lock.read().await.current_child.read().await.id();
let pid = proc_lock println!("Minecraft UUID: {}", uuid);
.read() println!("Minecraft PID: {:?}", pid);
.await
.child
.id()
.expect("Could not get PID from process.");
println!("Minecraft PID: {}", pid);
// Wait 5 seconds // Wait 5 seconds
println!("Waiting 5 seconds to gather logs..."); println!("Waiting 20 seconds to gather logs...");
sleep(Duration::from_secs(5)).await; sleep(Duration::from_secs(20)).await;
let _stdout = process::get_stdout_by_pid(pid).await?; let stdout = process::get_stdout_by_uuid(&uuid).await?;
let _stderr = process::get_stderr_by_pid(pid).await?; println!("Logs after 5sec <<< {stdout} >>> end stdout");
// println!("Logs after 5sec <<< {stdout} >>> end stdout");
println!(
"All running process UUID {:?}",
process::get_all_running_uuids().await?
);
println!( println!(
"All running process paths {:?}", "All running process paths {:?}",
process::get_all_running_profile_paths().await? process::get_all_running_profile_paths().await?