mirror of
https://github.com/open-goal/launcher.git
synced 2024-10-20 04:57:38 -04:00
UX: Stream logs to the frontend during installation process instead of only updating after each step. (#565)
This commit is contained in:
parent
7b9ade5def
commit
135f200f4a
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
|
@ -3193,6 +3193,7 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
|
|
|
@ -43,6 +43,7 @@ zip = { version = "2.2.0", features = ["deflate-zlib-ng"] }
|
||||||
zip-extract = "0.2.1"
|
zip-extract = "0.2.1"
|
||||||
tempfile = "3.12.0"
|
tempfile = "3.12.0"
|
||||||
native-dialog = "0.7.0"
|
native-dialog = "0.7.0"
|
||||||
|
tokio-util = "0.7.12"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.52.0"
|
winreg = "0.52.0"
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
use std::os::windows::process::CommandExt;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command,
|
process::Stdio,
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
|
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
@ -15,7 +14,10 @@ use tauri::Manager;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::LauncherConfig,
|
config::LauncherConfig,
|
||||||
util::file::{create_dir, overwrite_dir, read_last_lines_from_file},
|
util::{
|
||||||
|
file::{create_dir, overwrite_dir},
|
||||||
|
process::{create_log_file, create_std_log_file, watch_process},
|
||||||
|
},
|
||||||
TAURI_APP,
|
TAURI_APP,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -204,31 +206,6 @@ fn get_exec_location(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_log_file(
|
|
||||||
app_handle: &tauri::AppHandle,
|
|
||||||
name: &str,
|
|
||||||
append: bool,
|
|
||||||
) -> Result<std::fs::File, CommandError> {
|
|
||||||
let log_path = &match app_handle.path_resolver().app_log_dir() {
|
|
||||||
None => {
|
|
||||||
return Err(CommandError::Installation(
|
|
||||||
"Could not determine path to save installation logs".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Some(path) => path,
|
|
||||||
};
|
|
||||||
create_dir(log_path)?;
|
|
||||||
let mut file_options = std::fs::OpenOptions::new();
|
|
||||||
file_options.create(true);
|
|
||||||
if append {
|
|
||||||
file_options.append(true);
|
|
||||||
} else {
|
|
||||||
file_options.write(true).truncate(true);
|
|
||||||
}
|
|
||||||
let file = file_options.open(log_path.join(name))?;
|
|
||||||
Ok(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct InstallStepOutput {
|
pub struct InstallStepOutput {
|
||||||
|
@ -252,18 +229,6 @@ pub async fn update_data_directory(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn get_end_of_logs(app_handle: tauri::AppHandle) -> Result<String, CommandError> {
|
|
||||||
Ok(read_last_lines_from_file(
|
|
||||||
&app_handle
|
|
||||||
.path_resolver()
|
|
||||||
.app_log_dir()
|
|
||||||
.unwrap()
|
|
||||||
.join("extractor.log"),
|
|
||||||
250,
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn extract_and_validate_iso(
|
pub async fn extract_and_validate_iso(
|
||||||
config: tauri::State<'_, tokio::sync::Mutex<LauncherConfig>>,
|
config: tauri::State<'_, tokio::sync::Mutex<LauncherConfig>>,
|
||||||
|
@ -306,23 +271,34 @@ pub async fn extract_and_validate_iso(
|
||||||
args.push(game_name.clone());
|
args.push(game_name.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the first install step, reset the file
|
|
||||||
let log_file = create_log_file(&app_handle, "extractor.log", false)?;
|
|
||||||
|
|
||||||
log::info!("Running extractor with args: {:?}", args);
|
log::info!("Running extractor with args: {:?}", args);
|
||||||
|
|
||||||
let mut command = Command::new(exec_info.executable_path);
|
let mut command = Command::new(exec_info.executable_path);
|
||||||
command
|
command
|
||||||
.args(args)
|
.args(args)
|
||||||
.current_dir(exec_info.executable_dir)
|
.stdout(Stdio::piped())
|
||||||
.stdout(log_file.try_clone()?)
|
.stderr(Stdio::piped())
|
||||||
.stderr(log_file.try_clone()?);
|
.current_dir(exec_info.executable_dir);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
command.creation_flags(0x08000000);
|
command.creation_flags(0x08000000);
|
||||||
}
|
}
|
||||||
let output = command.output()?;
|
let mut child = command.spawn()?;
|
||||||
match output.status.code() {
|
|
||||||
|
// This is the first install step, reset the file
|
||||||
|
let mut log_file =
|
||||||
|
create_log_file(&app_handle, format!("extractor-{game_name}.log"), true).await?;
|
||||||
|
|
||||||
|
let process_status = watch_process(&mut log_file, &mut child, &app_handle).await?;
|
||||||
|
log_file.flush().await?;
|
||||||
|
if process_status.is_none() {
|
||||||
|
log::error!("extraction and validation was not successful. No status code");
|
||||||
|
return Ok(InstallStepOutput {
|
||||||
|
success: false,
|
||||||
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
match process_status.unwrap().code() {
|
||||||
Some(code) => {
|
Some(code) => {
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
log::info!("extraction and validation was successful");
|
log::info!("extraction and validation was successful");
|
||||||
|
@ -337,8 +313,6 @@ pub async fn extract_and_validate_iso(
|
||||||
};
|
};
|
||||||
let message = error_code_map.get(&code).unwrap_or(&default_error);
|
let message = error_code_map.get(&code).unwrap_or(&default_error);
|
||||||
log::error!("extraction and validation was not successful. Code {code}");
|
log::error!("extraction and validation was not successful. Code {code}");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some(message.msg.clone()),
|
msg: Some(message.msg.clone()),
|
||||||
|
@ -346,8 +320,6 @@ pub async fn extract_and_validate_iso(
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::error!("extraction and validation was not successful. No status code");
|
log::error!("extraction and validation was not successful. No status code");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some("Unexpected error occurred".to_owned()),
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
@ -392,7 +364,6 @@ pub async fn run_decompiler(
|
||||||
.to_string();
|
.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let log_file = create_log_file(&app_handle, "extractor.log", !truncate_logs)?;
|
|
||||||
let mut command = Command::new(exec_info.executable_path);
|
let mut command = Command::new(exec_info.executable_path);
|
||||||
|
|
||||||
let mut decomp_config_overrides = vec![];
|
let mut decomp_config_overrides = vec![];
|
||||||
|
@ -442,15 +413,35 @@ pub async fn run_decompiler(
|
||||||
|
|
||||||
command
|
command
|
||||||
.args(args)
|
.args(args)
|
||||||
.stdout(log_file.try_clone()?)
|
.stdout(Stdio::piped())
|
||||||
.stderr(log_file)
|
.stderr(Stdio::piped())
|
||||||
.current_dir(exec_info.executable_dir);
|
.current_dir(exec_info.executable_dir);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
command.creation_flags(0x08000000);
|
command.creation_flags(0x08000000);
|
||||||
}
|
}
|
||||||
let output = command.output()?;
|
|
||||||
match output.status.code() {
|
let mut child = command.spawn()?;
|
||||||
|
|
||||||
|
let mut log_file = create_log_file(
|
||||||
|
&app_handle,
|
||||||
|
format!("extractor-{game_name}.log"),
|
||||||
|
!truncate_logs,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let process_status = watch_process(&mut log_file, &mut child, &app_handle).await?;
|
||||||
|
|
||||||
|
// Ensure all remaining data is flushed to the file
|
||||||
|
log_file.flush().await?;
|
||||||
|
if process_status.is_none() {
|
||||||
|
log::error!("decompilation was not successful. No status code");
|
||||||
|
return Ok(InstallStepOutput {
|
||||||
|
success: false,
|
||||||
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
match process_status.unwrap().code() {
|
||||||
Some(code) => {
|
Some(code) => {
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
log::info!("decompilation was successful");
|
log::info!("decompilation was successful");
|
||||||
|
@ -465,8 +456,6 @@ pub async fn run_decompiler(
|
||||||
};
|
};
|
||||||
let message = error_code_map.get(&code).unwrap_or(&default_error);
|
let message = error_code_map.get(&code).unwrap_or(&default_error);
|
||||||
log::error!("decompilation was not successful. Code {code}");
|
log::error!("decompilation was not successful. Code {code}");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some(message.msg.clone()),
|
msg: Some(message.msg.clone()),
|
||||||
|
@ -474,8 +463,6 @@ pub async fn run_decompiler(
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::error!("decompilation was not successful. No status code");
|
log::error!("decompilation was not successful. No status code");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some("Unexpected error occurred".to_owned()),
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
@ -519,7 +506,6 @@ pub async fn run_compiler(
|
||||||
.to_string();
|
.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let log_file = create_log_file(&app_handle, "extractor.log", !truncate_logs)?;
|
|
||||||
let mut args = vec![
|
let mut args = vec![
|
||||||
source_path,
|
source_path,
|
||||||
"--compile".to_string(),
|
"--compile".to_string(),
|
||||||
|
@ -537,15 +523,25 @@ pub async fn run_compiler(
|
||||||
let mut command = Command::new(exec_info.executable_path);
|
let mut command = Command::new(exec_info.executable_path);
|
||||||
command
|
command
|
||||||
.args(args)
|
.args(args)
|
||||||
.stdout(log_file.try_clone().unwrap())
|
.stdout(Stdio::piped())
|
||||||
.stderr(log_file)
|
.stderr(Stdio::piped())
|
||||||
.current_dir(exec_info.executable_dir);
|
.current_dir(exec_info.executable_dir);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
command.creation_flags(0x08000000);
|
command.creation_flags(0x08000000);
|
||||||
}
|
}
|
||||||
let output = command.output()?;
|
let mut child = command.spawn()?;
|
||||||
match output.status.code() {
|
|
||||||
|
let mut log_file = create_log_file(
|
||||||
|
&app_handle,
|
||||||
|
format!("extractor-{game_name}.log"),
|
||||||
|
!truncate_logs,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let process_status = watch_process(&mut log_file, &mut child, &app_handle).await?;
|
||||||
|
log_file.flush().await?;
|
||||||
|
match process_status.unwrap().code() {
|
||||||
Some(code) => {
|
Some(code) => {
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
log::info!("compilation was successful");
|
log::info!("compilation was successful");
|
||||||
|
@ -560,8 +556,6 @@ pub async fn run_compiler(
|
||||||
};
|
};
|
||||||
let message = error_code_map.get(&code).unwrap_or(&default_error);
|
let message = error_code_map.get(&code).unwrap_or(&default_error);
|
||||||
log::error!("compilation was not successful. Code {code}");
|
log::error!("compilation was not successful. Code {code}");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some(message.msg.clone()),
|
msg: Some(message.msg.clone()),
|
||||||
|
@ -569,8 +563,6 @@ pub async fn run_compiler(
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::error!("compilation was not successful. No status code");
|
log::error!("compilation was not successful. No status code");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some("Unexpected error occurred".to_owned()),
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
@ -657,7 +649,7 @@ pub async fn run_game_gpu_test(
|
||||||
{
|
{
|
||||||
command.creation_flags(0x08000000);
|
command.creation_flags(0x08000000);
|
||||||
}
|
}
|
||||||
let output = command.output()?;
|
let output = command.output().await?;
|
||||||
match output.status.code() {
|
match output.status.code() {
|
||||||
Some(code) => {
|
Some(code) => {
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
|
@ -782,10 +774,9 @@ pub async fn launch_game(
|
||||||
args
|
args
|
||||||
);
|
);
|
||||||
|
|
||||||
let log_file = create_log_file(&app_handle, "game.log", false)?;
|
let log_file = create_std_log_file(&app_handle, format!("game-{game_name}.log"), false)?;
|
||||||
|
|
||||||
// TODO - log rotation here would be nice too, and for it to be game specific
|
let mut command = std::process::Command::new(exec_info.executable_path);
|
||||||
let mut command = Command::new(exec_info.executable_path);
|
|
||||||
command
|
command
|
||||||
.args(args)
|
.args(args)
|
||||||
.stdout(log_file.try_clone().unwrap())
|
.stdout(log_file.try_clone().unwrap())
|
||||||
|
@ -793,7 +784,7 @@ pub async fn launch_game(
|
||||||
.current_dir(exec_info.executable_dir);
|
.current_dir(exec_info.executable_dir);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
command.creation_flags(0x08000000);
|
std::os::windows::process::CommandExt::creation_flags(&mut command, 0x08000000);
|
||||||
}
|
}
|
||||||
// Start the process here so if there is an error, we can return immediately
|
// Start the process here so if there is an error, we can return immediately
|
||||||
let mut child = command.spawn()?;
|
let mut child = command.spawn()?;
|
||||||
|
|
|
@ -3,10 +3,11 @@ use std::os::windows::process::CommandExt;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command,
|
process::Stdio,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{binaries::InstallStepOutput, CommandError},
|
commands::{binaries::InstallStepOutput, CommandError},
|
||||||
|
@ -14,6 +15,7 @@ use crate::{
|
||||||
util::{
|
util::{
|
||||||
file::{create_dir, delete_dir, to_image_base64},
|
file::{create_dir, delete_dir, to_image_base64},
|
||||||
network::download_file,
|
network::download_file,
|
||||||
|
process::{create_log_file, create_std_log_file, watch_process},
|
||||||
tar::{extract_and_delete_tar_ball, extract_tar_ball},
|
tar::{extract_and_delete_tar_ball, extract_tar_ball},
|
||||||
zip::{extract_and_delete_zip_file, extract_zip_file},
|
zip::{extract_and_delete_zip_file, extract_zip_file},
|
||||||
},
|
},
|
||||||
|
@ -252,31 +254,6 @@ fn get_mod_exec_location(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_log_file(
|
|
||||||
app_handle: &tauri::AppHandle,
|
|
||||||
name: &str,
|
|
||||||
append: bool,
|
|
||||||
) -> Result<std::fs::File, CommandError> {
|
|
||||||
let log_path = &match app_handle.path_resolver().app_log_dir() {
|
|
||||||
None => {
|
|
||||||
return Err(CommandError::Installation(
|
|
||||||
"Could not determine path to save installation logs".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Some(path) => path,
|
|
||||||
};
|
|
||||||
create_dir(log_path)?;
|
|
||||||
let mut file_options = std::fs::OpenOptions::new();
|
|
||||||
file_options.create(true);
|
|
||||||
if append {
|
|
||||||
file_options.append(true);
|
|
||||||
} else {
|
|
||||||
file_options.write(true).truncate(true);
|
|
||||||
}
|
|
||||||
let file = file_options.open(log_path.join(name))?;
|
|
||||||
Ok(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct LauncherErrorCode {
|
struct LauncherErrorCode {
|
||||||
msg: String,
|
msg: String,
|
||||||
|
@ -336,23 +313,37 @@ pub async fn extract_iso_for_mod_install(
|
||||||
game_name.clone(),
|
game_name.clone(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// This is the first install step, reset the file
|
|
||||||
let log_file = create_log_file(&app_handle, "extractor.log", false)?;
|
|
||||||
|
|
||||||
log::info!("Running extractor with args: {:?}", args);
|
log::info!("Running extractor with args: {:?}", args);
|
||||||
|
|
||||||
let mut command = Command::new(exec_info.executable_path);
|
let mut command = Command::new(exec_info.executable_path);
|
||||||
command
|
command
|
||||||
.args(args)
|
.args(args)
|
||||||
.current_dir(exec_info.executable_dir)
|
.current_dir(exec_info.executable_dir)
|
||||||
.stdout(log_file.try_clone()?)
|
.stdout(Stdio::piped())
|
||||||
.stderr(log_file.try_clone()?);
|
.stderr(Stdio::piped());
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
command.creation_flags(0x08000000);
|
command.creation_flags(0x08000000);
|
||||||
}
|
}
|
||||||
let output = command.output()?;
|
let mut child = command.spawn()?;
|
||||||
match output.status.code() {
|
|
||||||
|
// This is the first install step, reset the file
|
||||||
|
let mut log_file = create_log_file(
|
||||||
|
&app_handle,
|
||||||
|
format!("extractor-{game_name}-{mod_name}.log"),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let process_status = watch_process(&mut log_file, &mut child, &app_handle).await?;
|
||||||
|
if process_status.is_none() {
|
||||||
|
log::error!("extraction and validation was not successful. No status code");
|
||||||
|
return Ok(InstallStepOutput {
|
||||||
|
success: false,
|
||||||
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
match process_status.unwrap().code() {
|
||||||
Some(code) => {
|
Some(code) => {
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
log::info!("extraction and validation was successful");
|
log::info!("extraction and validation was successful");
|
||||||
|
@ -365,8 +356,6 @@ pub async fn extract_iso_for_mod_install(
|
||||||
msg: format!("Unexpected error occured with code {code}"),
|
msg: format!("Unexpected error occured with code {code}"),
|
||||||
};
|
};
|
||||||
log::error!("extraction and validation was not successful. Code {code}");
|
log::error!("extraction and validation was not successful. Code {code}");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some(default_error.msg.clone()),
|
msg: Some(default_error.msg.clone()),
|
||||||
|
@ -374,8 +363,6 @@ pub async fn extract_iso_for_mod_install(
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::error!("extraction and validation was not successful. No status code");
|
log::error!("extraction and validation was not successful. No status code");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some("Unexpected error occurred".to_owned()),
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
@ -434,26 +421,38 @@ pub async fn decompile_for_mod_install(
|
||||||
game_name.clone(),
|
game_name.clone(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// This is the first install step, reset the file
|
|
||||||
let log_file = create_log_file(&app_handle, "extractor.log", false)?;
|
|
||||||
|
|
||||||
log::info!("Running extractor with args: {:?}", args);
|
log::info!("Running extractor with args: {:?}", args);
|
||||||
|
|
||||||
let mut command = Command::new(exec_info.executable_path);
|
let mut command = Command::new(exec_info.executable_path);
|
||||||
command
|
command
|
||||||
.args(args)
|
.args(args)
|
||||||
.current_dir(exec_info.executable_dir)
|
.current_dir(exec_info.executable_dir)
|
||||||
.stdout(log_file.try_clone()?)
|
.stdout(Stdio::piped())
|
||||||
.stderr(log_file.try_clone()?);
|
.stderr(Stdio::piped());
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
command.creation_flags(0x08000000);
|
command.creation_flags(0x08000000);
|
||||||
}
|
}
|
||||||
let output = command.output()?;
|
let mut child = command.spawn()?;
|
||||||
match output.status.code() {
|
|
||||||
|
let mut log_file =
|
||||||
|
create_log_file(&app_handle, format!("extractor-{game_name}.log"), false).await?;
|
||||||
|
|
||||||
|
let process_status = watch_process(&mut log_file, &mut child, &app_handle).await?;
|
||||||
|
|
||||||
|
// Ensure all remaining data is flushed to the file
|
||||||
|
log_file.flush().await?;
|
||||||
|
if process_status.is_none() {
|
||||||
|
log::error!("decompilation was not successful. No status code");
|
||||||
|
return Ok(InstallStepOutput {
|
||||||
|
success: false,
|
||||||
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
match process_status.unwrap().code() {
|
||||||
Some(code) => {
|
Some(code) => {
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
log::info!("extraction and validation was successful");
|
log::info!("decompilation was successful");
|
||||||
return Ok(InstallStepOutput {
|
return Ok(InstallStepOutput {
|
||||||
success: true,
|
success: true,
|
||||||
msg: None,
|
msg: None,
|
||||||
|
@ -462,18 +461,14 @@ pub async fn decompile_for_mod_install(
|
||||||
let default_error = LauncherErrorCode {
|
let default_error = LauncherErrorCode {
|
||||||
msg: format!("Unexpected error occured with code {code}"),
|
msg: format!("Unexpected error occured with code {code}"),
|
||||||
};
|
};
|
||||||
log::error!("extraction and validation was not successful. Code {code}");
|
log::error!("decompilation was not successful. Code {code}");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some(default_error.msg.clone()),
|
msg: Some(default_error.msg.clone()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::error!("extraction and validation was not successful. No status code");
|
log::error!("decompilation was not successful. No status code");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some("Unexpected error occurred".to_owned()),
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
@ -532,26 +527,36 @@ pub async fn compile_for_mod_install(
|
||||||
game_name.clone(),
|
game_name.clone(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// This is the first install step, reset the file
|
|
||||||
let log_file = create_log_file(&app_handle, "extractor.log", false)?;
|
|
||||||
|
|
||||||
log::info!("Running extractor with args: {:?}", args);
|
log::info!("Running extractor with args: {:?}", args);
|
||||||
|
|
||||||
let mut command = Command::new(exec_info.executable_path);
|
let mut command = Command::new(exec_info.executable_path);
|
||||||
command
|
command
|
||||||
.args(args)
|
.args(args)
|
||||||
.current_dir(exec_info.executable_dir)
|
.current_dir(exec_info.executable_dir)
|
||||||
.stdout(log_file.try_clone()?)
|
.stdout(Stdio::piped())
|
||||||
.stderr(log_file.try_clone()?);
|
.stderr(Stdio::piped());
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
command.creation_flags(0x08000000);
|
command.creation_flags(0x08000000);
|
||||||
}
|
}
|
||||||
let output = command.output()?;
|
let mut child = command.spawn()?;
|
||||||
match output.status.code() {
|
|
||||||
|
let mut log_file =
|
||||||
|
create_log_file(&app_handle, format!("extractor-{game_name}.log"), false).await?;
|
||||||
|
|
||||||
|
let process_status = watch_process(&mut log_file, &mut child, &app_handle).await?;
|
||||||
|
log_file.flush().await?;
|
||||||
|
if process_status.is_none() {
|
||||||
|
log::error!("compilation was not successful. No status code");
|
||||||
|
return Ok(InstallStepOutput {
|
||||||
|
success: false,
|
||||||
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
match process_status.unwrap().code() {
|
||||||
Some(code) => {
|
Some(code) => {
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
log::info!("extraction and validation was successful");
|
log::info!("compilation was successful");
|
||||||
return Ok(InstallStepOutput {
|
return Ok(InstallStepOutput {
|
||||||
success: true,
|
success: true,
|
||||||
msg: None,
|
msg: None,
|
||||||
|
@ -560,18 +565,14 @@ pub async fn compile_for_mod_install(
|
||||||
let default_error = LauncherErrorCode {
|
let default_error = LauncherErrorCode {
|
||||||
msg: format!("Unexpected error occured with code {code}"),
|
msg: format!("Unexpected error occured with code {code}"),
|
||||||
};
|
};
|
||||||
log::error!("extraction and validation was not successful. Code {code}");
|
log::error!("compilation was not successful. Code {code}");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some(default_error.msg.clone()),
|
msg: Some(default_error.msg.clone()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::error!("extraction and validation was not successful. No status code");
|
log::error!("compilation was not successful. No status code");
|
||||||
log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
log::error!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
||||||
Ok(InstallStepOutput {
|
Ok(InstallStepOutput {
|
||||||
success: false,
|
success: false,
|
||||||
msg: Some("Unexpected error occurred".to_owned()),
|
msg: Some("Unexpected error occurred".to_owned()),
|
||||||
|
@ -686,13 +687,17 @@ pub async fn launch_mod(
|
||||||
&mod_name,
|
&mod_name,
|
||||||
&source_name,
|
&source_name,
|
||||||
)?;
|
)?;
|
||||||
let args = generate_launch_mod_args(game_name, in_debug, config_dir, false)?;
|
let args = generate_launch_mod_args(game_name.clone(), in_debug, config_dir, false)?;
|
||||||
|
|
||||||
log::info!("Launching gk args: {:?}", args);
|
log::info!("Launching gk args: {:?}", args);
|
||||||
|
|
||||||
let log_file = create_log_file(&app_handle, "mod.log", false)?;
|
let log_file = create_std_log_file(
|
||||||
|
&app_handle,
|
||||||
|
format!("game-{game_name}-{mod_name}.log"),
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
// TODO - log rotation here would be nice too, and for it to be game/mod specific
|
// TODO - log rotation here would be nice too
|
||||||
let mut command = Command::new(exec_info.executable_path);
|
let mut command = Command::new(exec_info.executable_path);
|
||||||
command
|
command
|
||||||
.args(args)
|
.args(args)
|
||||||
|
|
|
@ -153,7 +153,6 @@ fn main() {
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
commands::binaries::extract_and_validate_iso,
|
commands::binaries::extract_and_validate_iso,
|
||||||
commands::binaries::get_end_of_logs,
|
|
||||||
commands::binaries::get_launch_game_string,
|
commands::binaries::get_launch_game_string,
|
||||||
commands::binaries::launch_game,
|
commands::binaries::launch_game,
|
||||||
commands::binaries::open_repl,
|
commands::binaries::open_repl,
|
||||||
|
|
|
@ -2,5 +2,6 @@ pub mod file;
|
||||||
pub mod game_milestones;
|
pub mod game_milestones;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod os;
|
pub mod os;
|
||||||
|
pub mod process;
|
||||||
pub mod tar;
|
pub mod tar;
|
||||||
pub mod zip;
|
pub mod zip;
|
||||||
|
|
|
@ -47,24 +47,6 @@ pub fn read_lines_in_file(path: &PathBuf) -> Result<String, Box<dyn std::error::
|
||||||
Ok(std::fs::read_to_string(path)?)
|
Ok(std::fs::read_to_string(path)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_last_lines_from_file(path: &PathBuf, lines: usize) -> Result<String, std::io::Error> {
|
|
||||||
if !path.exists() {
|
|
||||||
return Ok("".to_owned());
|
|
||||||
}
|
|
||||||
let buf = rev_buf_reader::RevBufReader::new(std::fs::File::open(path)?);
|
|
||||||
Ok(
|
|
||||||
buf
|
|
||||||
.lines()
|
|
||||||
.take(lines)
|
|
||||||
.map(|l| l.unwrap_or("".to_owned()))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.into_iter()
|
|
||||||
.rev()
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn touch_file(path: &PathBuf) -> std::io::Result<()> {
|
pub fn touch_file(path: &PathBuf) -> std::io::Result<()> {
|
||||||
match std::fs::OpenOptions::new()
|
match std::fs::OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
|
|
119
src-tauri/src/util/process.rs
Normal file
119
src-tauri/src/util/process.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use std::{process::ExitStatus, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
use tokio::{
|
||||||
|
io::{AsyncBufReadExt, AsyncWriteExt},
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::commands::CommandError;
|
||||||
|
|
||||||
|
use super::file::create_dir;
|
||||||
|
use tauri::Manager;
|
||||||
|
|
||||||
|
pub async fn create_log_file(
|
||||||
|
app_handle: &tauri::AppHandle,
|
||||||
|
name: String,
|
||||||
|
append: bool,
|
||||||
|
) -> Result<tokio::fs::File, CommandError> {
|
||||||
|
let log_path = &match app_handle.path_resolver().app_log_dir() {
|
||||||
|
None => {
|
||||||
|
return Err(CommandError::Installation(
|
||||||
|
"Could not determine path to save installation logs".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(path) => path,
|
||||||
|
};
|
||||||
|
create_dir(log_path)?;
|
||||||
|
let mut file_options = tokio::fs::OpenOptions::new();
|
||||||
|
file_options.read(true);
|
||||||
|
file_options.create(true);
|
||||||
|
if append {
|
||||||
|
file_options.append(true);
|
||||||
|
} else {
|
||||||
|
file_options.write(true).truncate(true);
|
||||||
|
}
|
||||||
|
let file = file_options.open(log_path.join(name)).await?;
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize)]
|
||||||
|
struct LogPayload {
|
||||||
|
logs: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn watch_process(
|
||||||
|
log_file: &mut tokio::fs::File,
|
||||||
|
child: &mut tokio::process::Child,
|
||||||
|
app_handle: &tauri::AppHandle,
|
||||||
|
) -> Result<Option<ExitStatus>, CommandError> {
|
||||||
|
let stdout = child.stdout.take().unwrap();
|
||||||
|
let stderr = child.stderr.take().unwrap();
|
||||||
|
|
||||||
|
let mut stdout_reader = tokio::io::BufReader::new(stdout).lines();
|
||||||
|
let mut stderr_reader = tokio::io::BufReader::new(stderr).lines();
|
||||||
|
let combined_buffer = Arc::new(Mutex::new(String::new()));
|
||||||
|
|
||||||
|
let mut interval = tokio::time::interval(Duration::from_millis(25));
|
||||||
|
|
||||||
|
let mut process_status = None;
|
||||||
|
loop {
|
||||||
|
let buffer_clone = Arc::clone(&combined_buffer);
|
||||||
|
tokio::select! {
|
||||||
|
Ok(Some(line)) = stdout_reader.next_line() => {
|
||||||
|
let formatted_line = format!("{line}\n");
|
||||||
|
log_file.write_all(formatted_line.as_bytes()).await?;
|
||||||
|
if formatted_line != "\n" {
|
||||||
|
let mut buf = buffer_clone.lock().await;
|
||||||
|
buf.push_str(&formatted_line);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(Some(line)) = stderr_reader.next_line() => {
|
||||||
|
let formatted_line = format!("{line}\n");
|
||||||
|
log_file.write_all(formatted_line.as_bytes()).await?;
|
||||||
|
if formatted_line != "\n" {
|
||||||
|
let mut buf = buffer_clone.lock().await;
|
||||||
|
buf.push_str(&formatted_line);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = interval.tick() => {
|
||||||
|
log_file.flush().await?;
|
||||||
|
{
|
||||||
|
let mut buf = buffer_clone.lock().await;
|
||||||
|
let _ = app_handle.emit_all("log_update", LogPayload { logs: buf.clone() });
|
||||||
|
buf.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Wait for the child process to finish
|
||||||
|
status = child.wait() => {
|
||||||
|
process_status = Some(status?);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(process_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_std_log_file(
|
||||||
|
app_handle: &tauri::AppHandle,
|
||||||
|
name: String,
|
||||||
|
append: bool,
|
||||||
|
) -> Result<std::fs::File, CommandError> {
|
||||||
|
let log_path = &match app_handle.path_resolver().app_log_dir() {
|
||||||
|
None => {
|
||||||
|
return Err(CommandError::Installation(
|
||||||
|
"Could not determine path to save installation logs".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(path) => path,
|
||||||
|
};
|
||||||
|
create_dir(log_path)?;
|
||||||
|
let mut file_options = std::fs::OpenOptions::new();
|
||||||
|
file_options.create(true);
|
||||||
|
if append {
|
||||||
|
file_options.append(true);
|
||||||
|
} else {
|
||||||
|
file_options.write(true).truncate(true);
|
||||||
|
}
|
||||||
|
let file = file_options.open(log_path.join(name))?;
|
||||||
|
Ok(file)
|
||||||
|
}
|
|
@ -7,7 +7,6 @@
|
||||||
import type { Job } from "$lib/utils/jobs";
|
import type { Job } from "$lib/utils/jobs";
|
||||||
import { getInternalName, type SupportedGame } from "$lib/constants";
|
import { getInternalName, type SupportedGame } from "$lib/constants";
|
||||||
import {
|
import {
|
||||||
getEndOfLogs,
|
|
||||||
runCompiler,
|
runCompiler,
|
||||||
runDecompiler,
|
runDecompiler,
|
||||||
updateDataDirectory,
|
updateDataDirectory,
|
||||||
|
@ -60,7 +59,6 @@
|
||||||
]);
|
]);
|
||||||
progressTracker.start();
|
progressTracker.start();
|
||||||
let resp = await runDecompiler("", getInternalName(activeGame), true);
|
let resp = await runDecompiler("", getInternalName(activeGame), true);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -84,7 +82,6 @@
|
||||||
]);
|
]);
|
||||||
progressTracker.start();
|
progressTracker.start();
|
||||||
let resp = await runCompiler("", getInternalName(activeGame), true);
|
let resp = await runCompiler("", getInternalName(activeGame), true);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -116,7 +113,6 @@
|
||||||
]);
|
]);
|
||||||
progressTracker.start();
|
progressTracker.start();
|
||||||
let resp = await updateDataDirectory(getInternalName(activeGame));
|
let resp = await updateDataDirectory(getInternalName(activeGame));
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -124,7 +120,6 @@
|
||||||
}
|
}
|
||||||
progressTracker.proceed();
|
progressTracker.proceed();
|
||||||
resp = await runDecompiler("", getInternalName(activeGame), true);
|
resp = await runDecompiler("", getInternalName(activeGame), true);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -132,7 +127,6 @@
|
||||||
}
|
}
|
||||||
progressTracker.proceed();
|
progressTracker.proceed();
|
||||||
resp = await runCompiler("", getInternalName(activeGame));
|
resp = await runCompiler("", getInternalName(activeGame));
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -198,7 +192,6 @@
|
||||||
}
|
}
|
||||||
progressTracker.proceed();
|
progressTracker.proceed();
|
||||||
resp = await runDecompiler("", getInternalName(activeGame), true);
|
resp = await runDecompiler("", getInternalName(activeGame), true);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -248,7 +241,6 @@
|
||||||
modSourceName,
|
modSourceName,
|
||||||
sourcePath,
|
sourcePath,
|
||||||
);
|
);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -266,7 +258,6 @@
|
||||||
modName,
|
modName,
|
||||||
modSourceName,
|
modSourceName,
|
||||||
);
|
);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -278,7 +269,6 @@
|
||||||
modName,
|
modName,
|
||||||
modSourceName,
|
modSourceName,
|
||||||
);
|
);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -338,7 +328,6 @@
|
||||||
modSourceName,
|
modSourceName,
|
||||||
sourcePath,
|
sourcePath,
|
||||||
);
|
);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -368,7 +357,6 @@
|
||||||
modName,
|
modName,
|
||||||
modSourceName,
|
modSourceName,
|
||||||
);
|
);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -380,7 +368,6 @@
|
||||||
modName,
|
modName,
|
||||||
modSourceName,
|
modSourceName,
|
||||||
);
|
);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -420,7 +407,6 @@
|
||||||
modName,
|
modName,
|
||||||
modSourceName,
|
modSourceName,
|
||||||
);
|
);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -449,8 +435,6 @@
|
||||||
modName,
|
modName,
|
||||||
modSourceName,
|
modSourceName,
|
||||||
);
|
);
|
||||||
// TODO - stream logs
|
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -494,9 +478,7 @@
|
||||||
|
|
||||||
<div class="flex flex-col justify-content">
|
<div class="flex flex-col justify-content">
|
||||||
<Progress />
|
<Progress />
|
||||||
{#if $progressTracker.logs !== undefined}
|
|
||||||
<LogViewer />
|
<LogViewer />
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{#if $progressTracker.overallStatus === "success"}
|
{#if $progressTracker.overallStatus === "success"}
|
||||||
<div class="flex flex-col justify-end items-end mt-auto">
|
<div class="flex flex-col justify-end items-end mt-auto">
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
import { Alert, Button } from "flowbite-svelte";
|
import { Alert, Button } from "flowbite-svelte";
|
||||||
import {
|
import {
|
||||||
extractAndValidateISO,
|
extractAndValidateISO,
|
||||||
getEndOfLogs,
|
|
||||||
runCompiler,
|
runCompiler,
|
||||||
runDecompiler,
|
runDecompiler,
|
||||||
} from "$lib/rpc/binaries";
|
} from "$lib/rpc/binaries";
|
||||||
|
@ -92,7 +91,6 @@
|
||||||
sourcePath,
|
sourcePath,
|
||||||
getInternalName(activeGame),
|
getInternalName(activeGame),
|
||||||
);
|
);
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -100,7 +98,6 @@
|
||||||
}
|
}
|
||||||
progressTracker.proceed();
|
progressTracker.proceed();
|
||||||
resp = await runDecompiler(sourcePath, getInternalName(activeGame));
|
resp = await runDecompiler(sourcePath, getInternalName(activeGame));
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -108,7 +105,6 @@
|
||||||
}
|
}
|
||||||
progressTracker.proceed();
|
progressTracker.proceed();
|
||||||
resp = await runCompiler(sourcePath, getInternalName(activeGame));
|
resp = await runCompiler(sourcePath, getInternalName(activeGame));
|
||||||
progressTracker.updateLogs(await getEndOfLogs());
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
progressTracker.halt();
|
progressTracker.halt();
|
||||||
installationError = resp.msg;
|
installationError = resp.msg;
|
||||||
|
@ -129,11 +125,9 @@
|
||||||
{#if !requirementsMet}
|
{#if !requirementsMet}
|
||||||
<Requirements {activeGame} on:recheckRequirements={checkRequirements} />
|
<Requirements {activeGame} on:recheckRequirements={checkRequirements} />
|
||||||
{:else if installing}
|
{:else if installing}
|
||||||
<div class="flex flex-col justify-content">
|
<div class="flex flex-col justify-content shrink">
|
||||||
<Progress />
|
<Progress />
|
||||||
{#if $progressTracker.logs !== undefined}
|
|
||||||
<LogViewer />
|
<LogViewer />
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{#if $progressTracker.overallStatus === "success"}
|
{#if $progressTracker.overallStatus === "success"}
|
||||||
<div class="flex flex-col justify-end items-end mt-auto">
|
<div class="flex flex-col justify-end items-end mt-auto">
|
||||||
|
|
|
@ -1,31 +1,40 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { progressTracker } from "$lib/stores/ProgressStore";
|
import { progressTracker } from "$lib/stores/ProgressStore";
|
||||||
import IconDocument from "~icons/mdi/file-document-outline";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { Accordion, AccordionItem } from "flowbite-svelte";
|
|
||||||
import { ansiSpan } from "ansi-to-span";
|
import { ansiSpan } from "ansi-to-span";
|
||||||
import escapeHtml from "escape-html";
|
import escapeHtml from "escape-html";
|
||||||
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
|
||||||
|
let logListener: any = undefined;
|
||||||
|
let logElement;
|
||||||
|
|
||||||
|
const scrollToBottom = async (node) => {
|
||||||
|
node.scroll({ top: node.scrollHeight, behavior: "instant" });
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
logListener = await listen("log_update", (event) => {
|
||||||
|
progressTracker.appendLogs(event.payload.logs);
|
||||||
|
if (logElement) {
|
||||||
|
scrollToBottom(logElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (logListener !== undefined) {
|
||||||
|
logListener();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function convertLogColors(text) {
|
function convertLogColors(text) {
|
||||||
return ansiSpan(escapeHtml(text)).replaceAll("\n", "<br/>");
|
return ansiSpan(escapeHtml(text)).replaceAll("\n", "<br/>");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Accordion class="log-accordian p-0 mb-2">
|
{#if $progressTracker.logs}
|
||||||
<AccordionItem class="bg-slate-900 rounded p-[1rem]">
|
<pre
|
||||||
<span slot="header" class="text-sm font-semibold text-white flex gap-2">
|
class="rounded p-2 bg-[#141414] text-[11px] max-h-[300px] overflow-auto text-pretty font-mono"
|
||||||
<IconDocument />
|
bind:this={logElement}>{@html convertLogColors($progressTracker.logs)}</pre>
|
||||||
<span>{$_("setup_logs_header")}</span>
|
{/if}
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
slot="default"
|
|
||||||
class="bg-slate-900 px-4 max-h-52 overflow-y-scroll scrollbar"
|
|
||||||
>
|
|
||||||
<p class="py-4 text-clip overflow-hidden font-mono log-output">
|
|
||||||
...{$_("setup_logs_truncation")}:
|
|
||||||
<br />
|
|
||||||
{@html convertLogColors($progressTracker.logs)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
|
|
|
@ -60,3 +60,7 @@
|
||||||
.font-mono {
|
.font-mono {
|
||||||
font-family: "Noto Sans Mono", monospace !important;
|
font-family: "Noto Sans Mono", monospace !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.font-mono span {
|
||||||
|
font-family: "Noto Sans Mono", monospace !important;
|
||||||
|
}
|
||||||
|
|
|
@ -17,10 +17,6 @@ export async function updateDataDirectory(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEndOfLogs(): Promise<string> {
|
|
||||||
return await invoke_rpc("get_end_of_logs", {}, () => "");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function extractAndValidateISO(
|
export async function extractAndValidateISO(
|
||||||
pathToIso: string,
|
pathToIso: string,
|
||||||
gameName: string,
|
gameName: string,
|
||||||
|
|
|
@ -16,7 +16,7 @@ interface ProgressTracker {
|
||||||
currentStep: number;
|
currentStep: number;
|
||||||
overallStatus: ProgressStatus;
|
overallStatus: ProgressStatus;
|
||||||
steps: ProgressStep[];
|
steps: ProgressStep[];
|
||||||
logs: string;
|
logs: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const storeValue: ProgressTracker = {
|
const storeValue: ProgressTracker = {
|
||||||
|
@ -66,9 +66,12 @@ function createProgressTracker() {
|
||||||
val.steps[val.currentStep].status = "failed";
|
val.steps[val.currentStep].status = "failed";
|
||||||
return val;
|
return val;
|
||||||
}),
|
}),
|
||||||
updateLogs: (logs: string) =>
|
appendLogs: (logs: string) =>
|
||||||
update((val) => {
|
update((val) => {
|
||||||
val.logs = logs;
|
if (val.logs === undefined) {
|
||||||
|
val.logs = "";
|
||||||
|
}
|
||||||
|
val.logs += logs;
|
||||||
return val;
|
return val;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue