feat: Pass stdout + stderr + pid callbacks to frontend

This commit is contained in:
LynithDev 2024-03-31 15:24:41 +02:00
parent b77f48ed90
commit fbab841bbd
5 changed files with 97 additions and 23 deletions

View file

@ -1,10 +1,10 @@
use std::{io::{stderr, stdout}, path::PathBuf};
use std::{path::PathBuf, process::Stdio};
use anyhow::anyhow;
use async_trait::async_trait;
use chrono::{DateTime, Local};
use serde::{Deserialize, Serialize};
use tokio::process;
use tokio::{io::{AsyncBufReadExt, BufReader}, process};
use uuid::Uuid;
use crate::auth::Account;
@ -46,11 +46,17 @@ pub struct SetupInfo {
pub natives_dir: PathBuf,
}
pub struct LaunchCallbacks {
pub on_launch: Box<dyn FnMut(u32) + Send>,
pub on_stdout: Box<dyn FnMut(String) + Send>,
pub on_stderr: Box<dyn FnMut(String) + Send>,
}
#[async_trait]
pub trait ClientTrait<'a>: Send + Sync {
fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self where Self: Sized;
async fn launch(&self, info: LaunchInfo) -> crate::Result<()> {
async fn launch(&self, info: LaunchInfo, mut callbacks: LaunchCallbacks) -> crate::Result<i32> {
let manifest = &self.get_manifest().minecraft_manifest;
let args = get_arguments(manifest)?
@ -63,9 +69,10 @@ pub trait ClientTrait<'a>: Send + Sync {
.replace("${auth_access_token}", &info.account.access_token)
.replace("${auth_session}", "0") // TODO: Figure out how to get this session ID.
.replace("${user_properties}", "{}") // TODO: Figure out these properties.
.replace("${user_type}", "1");
.replace("${user_type}", "1")
.replace("${version_type}", "OneLauncher");
process::Command::new(&info.java).arg("-XX:-UseAdaptiveSizePolicy")
let mut process = process::Command::new(&info.java).arg("-XX:-UseAdaptiveSizePolicy")
.arg("-XX:-OmitStackTraceInFastThrow")
.arg("-Dminecraft.launcher.brand=onelauncher")
.arg("-Dminecraft.launcher.version=1")
@ -76,13 +83,36 @@ pub trait ClientTrait<'a>: Send + Sync {
.arg(info.setup.libraries)
.arg(manifest.main_class.clone())
.args(args.split_whitespace())
.stdout(stdout()) // TODO: Implement better logging
.stderr(stderr())
.spawn()?
.wait()
.await?;
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let pid = process.id().unwrap_or(0);
(callbacks.on_launch)(pid);
let stdout = process.stdout.take().ok_or(anyhow!("Couldn't take stdout"))?;
let stderr = process.stderr.take().ok_or(anyhow!("Couldn't take stderr"))?;
Ok(())
let mut stdout_lines = BufReader::new(stdout).lines();
let mut stderr_lines = BufReader::new(stderr).lines();
let stdout_task = tokio::spawn(async move {
while let Some(line) = stdout_lines.next_line().await.expect("Couldn't read line") {
(callbacks.on_stdout)(line);
}
});
let stderr_task = tokio::spawn(async move {
while let Some(line) = stderr_lines.next_line().await.expect("Couldn't read line") {
(callbacks.on_stderr)(line);
}
});
let status = process.wait().await?;
tokio::try_join!(stdout_task, stderr_task)?;
let exit_code = status.code().unwrap_or(0);
Ok(exit_code)
}
async fn setup(&self) -> crate::Result<SetupInfo>;

View file

@ -7,7 +7,7 @@ use uuid::Uuid;
use crate::{auth::Account, utils::dirs};
use super::{client::{ClientTrait, Cluster, LaunchInfo, Manifest}, clients::{self, vanilla, ClientType}, java};
use super::{client::{ClientTrait, Cluster, LaunchCallbacks, LaunchInfo, Manifest}, clients::{self, vanilla, ClientType}, java};
#[derive(Debug, thiserror::Error)]
pub enum ClientManagerError {
@ -75,7 +75,8 @@ impl ClientManager {
pub async fn launch_cluster(
&mut self,
uuid: Uuid,
) -> crate::Result<()> {
callbacks: LaunchCallbacks,
) -> crate::Result<i32> {
let client = self.get_impl_uuid(uuid)?;
let java = java::download_java(&dirs::java_dir()?, client.get_manifest().minecraft_manifest.java_version.major_version).await?;
@ -95,8 +96,8 @@ impl ClientManager {
mem_max: 4096,
};
client.launch(info).await?;
Ok(())
let exit_code = client.launch(info, callbacks).await?;
Ok(exit_code)
}
pub async fn create_cluster(

View file

@ -1,5 +1,5 @@
use onelauncher::game::{client::{Cluster, Manifest}, clients::ClientType};
use tauri::State;
use onelauncher::game::{client::{Cluster, LaunchCallbacks, Manifest}, clients::ClientType};
use tauri::{Manager, State};
use tokio::sync::Mutex;
use uuid::Uuid;
@ -41,12 +41,32 @@ pub async fn get_cluster(
#[tauri::command]
pub async fn launch_cluster(
app: tauri::AppHandle,
state: State<'_, Mutex<GameManagerState>>,
uuid: Uuid,
) -> Result<(), String> {
) -> Result<i32, String> {
let manager = &mut state.lock().await.client_manager;
manager.launch_cluster(uuid).await?;
Ok(())
let on_launch_app = app.clone();
let on_stdout_app = app.clone();
let on_stderr_app = app.clone();
let callbacks = LaunchCallbacks {
on_launch: Box::new(move |pid| {
on_launch_app.emit("game:launch", pid).expect("Failed to emit game:launched");
}),
on_stdout: Box::new(move |line| {
on_stdout_app.emit("game:stdout", line).expect("Failed to emit game:stdout");
}),
on_stderr: Box::new(move |line| {
on_stderr_app.emit("game:stderr", line).expect("Failed to emit game:stderr");
})
};
let exit_code = manager.launch_cluster(uuid, callbacks).await?;
Ok(exit_code)
}
#[tauri::command]

View file

@ -1,4 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
export async function getCluster(uuid: string): Promise<Core.Cluster> {
return await invoke<Core.Cluster>(
@ -61,9 +62,25 @@ export async function refreshClientManager(): Promise<void> {
return await invoke('plugin:onelauncher|refresh_client_manager');
}
export async function launchCluster(uuid: string): Promise<number> {
return await invoke<number>(
interface LaunchCallbacks {
on_launch: (pid: number) => any;
on_stdout: (line: string) => any;
on_stderr: (line: string) => any;
};
export async function launchCluster(uuid: string, callbacks: LaunchCallbacks): Promise<number> {
const unlisten_launch = await listen<number>('game:launch', e => callbacks.on_launch(e.payload));
const unlisten_stdout = await listen<string>('game:stdout', e => callbacks.on_stdout(e.payload));
const unlisten_stderr = await listen<string>('game:stderr', e => callbacks.on_stderr(e.payload));
const exit_code = await invoke<number>(
'plugin:onelauncher|launch_cluster',
{ uuid },
);
unlisten_launch();
unlisten_stdout();
unlisten_stderr();
return exit_code;
}

View file

@ -76,7 +76,13 @@ function ClusterCardContextMenu(props: ClusterCardContextMenuProps) {
}
function launch() {
launchCluster(props.id);
const exit_code = launchCluster(props.id, {
on_launch: pid => console.log('Launched with PID:', pid),
on_stdout: line => console.log('STDOUT:', line),
on_stderr: line => console.log('STDERR:', line),
});
console.log('Exit with code: ', exit_code);
}
return (