diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a68cb9b..1ef7850 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2273,6 +2273,7 @@ dependencies = [ "futures-util", "log", "reqwest", + "rev_buf_reader", "serde", "serde_json", "sysinfo", @@ -2902,6 +2903,15 @@ dependencies = [ "winreg", ] +[[package]] +name = "rev_buf_reader" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c0f2e47e00e29920959826e2e1784728a3780d1a784247be5257258cc75f910" +dependencies = [ + "memchr", +] + [[package]] name = "rfd" version = "0.10.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 809f9be..4ce5fed 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -32,6 +32,7 @@ wgpu = "0.15.1" walkdir = "2.3.2" dir-diff = "0.3.2" thiserror = "1.0.38" +rev_buf_reader = "0.3.0" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/commands/binaries.rs b/src-tauri/src/commands/binaries.rs index dd5e0ac..5398179 100644 --- a/src-tauri/src/commands/binaries.rs +++ b/src-tauri/src/commands/binaries.rs @@ -12,7 +12,7 @@ use tauri::Manager; use crate::{ config::LauncherConfig, - util::file::{create_dir, overwrite_dir, read_lines_in_file}, + util::file::{create_dir, overwrite_dir, read_last_lines_from_file, read_lines_in_file}, }; use super::CommandError; @@ -244,9 +244,16 @@ pub async fn update_data_directory( }) } -#[derive(Clone, serde::Serialize)] -struct LogPayload { - stdout: String, +#[tauri::command] +pub async fn get_end_of_logs(app_handle: tauri::AppHandle) -> Result { + Ok(read_last_lines_from_file( + &app_handle + .path_resolver() + .app_log_dir() + .unwrap() + .join("extractor.log"), + 250, + )?) } #[tauri::command] @@ -283,21 +290,6 @@ pub async fn extract_and_validate_iso( .stdout(log_file.try_clone().unwrap()) .stderr(log_file.try_clone().unwrap()) .output()?; - // TODO - we should instead capture the logs while simulanteously streaming them to a file - // right now we aren't so I just read the file after it's done - app_handle.emit_all( - "updateJobLogs", - LogPayload { - stdout: read_lines_in_file( - &app_handle - .path_resolver() - .app_log_dir() - .unwrap() - .join("extractor.log"), - ) - .expect("TODO"), - }, - )?; match output.status.code() { Some(code) => { if code == 0 { @@ -329,6 +321,7 @@ pub async fn run_decompiler( app_handle: tauri::AppHandle, path_to_iso: String, game_name: String, + truncate_logs: bool, ) -> Result { let config_lock = config.lock().await; let config_info = common_prelude(&config_lock)?; @@ -345,7 +338,7 @@ pub async fn run_decompiler( .to_string(); } - let log_file = create_log_file(&app_handle, "extractor.log", true)?; + let log_file = create_log_file(&app_handle, "extractor.log", !truncate_logs)?; let output = Command::new(&exec_info.executable_path) .creation_flags(0x08000000) .args([ @@ -358,21 +351,6 @@ pub async fn run_decompiler( .stderr(log_file) .current_dir(exec_info.executable_dir) .output()?; - // TODO - we should instead capture the logs while simulanteously streaming them to a file - // right now we aren't so I just read the file after it's done - app_handle.emit_all( - "updateJobLogs", - LogPayload { - stdout: read_lines_in_file( - &app_handle - .path_resolver() - .app_log_dir() - .unwrap() - .join("extractor.log"), - ) - .expect("TODO"), - }, - )?; match output.status.code() { Some(code) => { if code == 0 { @@ -404,6 +382,7 @@ pub async fn run_compiler( app_handle: tauri::AppHandle, path_to_iso: String, game_name: String, + truncate_logs: bool, ) -> Result { let config_lock = config.lock().await; let config_info = common_prelude(&config_lock)?; @@ -420,7 +399,7 @@ pub async fn run_compiler( .to_string(); } - let log_file = create_log_file(&app_handle, "extractor.log", true)?; + let log_file = create_log_file(&app_handle, "extractor.log", !truncate_logs)?; let output = Command::new(&exec_info.executable_path) .creation_flags(0x08000000) .args([ @@ -433,21 +412,6 @@ pub async fn run_compiler( .stderr(log_file) .current_dir(exec_info.executable_dir) .output()?; - // TODO - we should instead capture the logs while simulanteously streaming them to a file - // right now we aren't so I just read the file after it's done - app_handle.emit_all( - "updateJobLogs", - LogPayload { - stdout: read_lines_in_file( - &app_handle - .path_resolver() - .app_log_dir() - .unwrap() - .join("extractor.log"), - ) - .expect("TODO"), - }, - )?; match output.status.code() { Some(code) => { if code == 0 { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 876993d..b4d4960 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -78,6 +78,7 @@ fn main() { }) .invoke_handler(tauri::generate_handler![ commands::binaries::update_data_directory, + commands::binaries::get_end_of_logs, commands::binaries::extract_and_validate_iso, commands::binaries::launch_game, commands::binaries::open_repl, diff --git a/src-tauri/src/util/file.rs b/src-tauri/src/util/file.rs index 9118bf1..fda0486 100644 --- a/src-tauri/src/util/file.rs +++ b/src-tauri/src/util/file.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{io::BufRead, path::PathBuf}; pub fn delete_dir(path: &PathBuf) -> Result<(), std::io::Error> { if path.exists() && path.is_dir() { @@ -36,3 +36,21 @@ pub fn overwrite_dir(src: &PathBuf, dst: &PathBuf) -> Result<(), fs_extra::error pub fn read_lines_in_file(path: &PathBuf) -> Result> { Ok(std::fs::read_to_string(path)?) } + +pub fn read_last_lines_from_file(path: &PathBuf, lines: usize) -> Result { + 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::>() + .into_iter() + .rev() + .collect::>() + .join("\n"), + ) +} diff --git a/src/components/games/job/GameJob.svelte b/src/components/games/job/GameJob.svelte index 611ce53..1056452 100644 --- a/src/components/games/job/GameJob.svelte +++ b/src/components/games/job/GameJob.svelte @@ -9,13 +9,13 @@ import type { Job } from "$lib/jobs/jobs"; import { getInternalName, type SupportedGame } from "$lib/constants"; import { + getEndOfLogs, runCompiler, runDecompiler, updateDataDirectory, } from "$lib/rpc/binaries"; import { finalizeInstallation } from "$lib/rpc/config"; import { generateSupportPackage } from "$lib/rpc/support"; - import { listen } from "@tauri-apps/api/event"; export let activeGame: SupportedGame; export let jobType: Job; @@ -29,10 +29,6 @@ // It's used to provide almost the same interface as the normal installation, with logs, etc // but for arbitrary jobs. Such as updating versions, decompiling, or compiling. onMount(async () => { - const unlistenLogListener = await listen("updateJobLogs", async (event) => { - progressTracker.updateLogs(event.payload["stdout"]); - }); - if (jobType === "decompile") { installationError = undefined; progressTracker.init([ @@ -46,7 +42,8 @@ }, ]); progressTracker.start(); - let resp = await runDecompiler("", getInternalName(activeGame)); + let resp = await runDecompiler("", getInternalName(activeGame), true); + progressTracker.updateLogs(await getEndOfLogs()); if (!resp.success) { progressTracker.halt(); installationError = resp.msg; @@ -67,7 +64,8 @@ }, ]); progressTracker.start(); - let resp = await runCompiler("", getInternalName(activeGame)); + let resp = await runCompiler("", getInternalName(activeGame), true); + progressTracker.updateLogs(await getEndOfLogs()); if (!resp.success) { progressTracker.halt(); installationError = resp.msg; @@ -97,13 +95,15 @@ ]); progressTracker.start(); let resp = await updateDataDirectory(getInternalName(activeGame)); + progressTracker.updateLogs(await getEndOfLogs()); if (!resp.success) { progressTracker.halt(); installationError = resp.msg; return; } progressTracker.proceed(); - resp = await runDecompiler("", getInternalName(activeGame)); + resp = await runDecompiler("", getInternalName(activeGame), true); + progressTracker.updateLogs(await getEndOfLogs()); if (!resp.success) { progressTracker.halt(); installationError = resp.msg; @@ -111,6 +111,7 @@ } progressTracker.proceed(); resp = await runCompiler("", getInternalName(activeGame)); + progressTracker.updateLogs(await getEndOfLogs()); if (!resp.success) { progressTracker.halt(); installationError = resp.msg; diff --git a/src/components/games/setup/GameSetup.svelte b/src/components/games/setup/GameSetup.svelte index 94c9232..19e95af 100644 --- a/src/components/games/setup/GameSetup.svelte +++ b/src/components/games/setup/GameSetup.svelte @@ -13,6 +13,7 @@ import { Alert, Button } from "flowbite-svelte"; import { extractAndValidateISO, + getEndOfLogs, runCompiler, runDecompiler, } from "$lib/rpc/binaries"; @@ -26,7 +27,6 @@ import { progressTracker } from "$lib/stores/ProgressStore"; import { generateSupportPackage } from "$lib/rpc/support"; import { isOpenGLVersionSupported } from "$lib/sidecars/glewinfo"; - import { listen } from "@tauri-apps/api/event"; export let activeGame: SupportedGame; @@ -45,10 +45,6 @@ await setOpenGLRequirementMet(isOpenGLMet); } requirementsMet = isAvxMet && isOpenGLMet; - - const unlistenLogListener = await listen("updateJobLogs", async (event) => { - progressTracker.updateLogs(event.payload["stdout"]); - }); }); async function install(viaFolder: boolean) { @@ -88,6 +84,7 @@ sourcePath, getInternalName(activeGame) ); + progressTracker.updateLogs(await getEndOfLogs()); if (!resp.success) { progressTracker.halt(); installationError = resp.msg; @@ -95,6 +92,7 @@ } progressTracker.proceed(); resp = await runDecompiler(sourcePath, getInternalName(activeGame)); + progressTracker.updateLogs(await getEndOfLogs()); if (!resp.success) { progressTracker.halt(); installationError = resp.msg; @@ -102,6 +100,7 @@ } progressTracker.proceed(); resp = await runCompiler(sourcePath, getInternalName(activeGame)); + progressTracker.updateLogs(await getEndOfLogs()); if (!resp.success) { progressTracker.halt(); installationError = resp.msg; diff --git a/src/components/games/setup/LogViewer.svelte b/src/components/games/setup/LogViewer.svelte index 09643c7..ea40d33 100644 --- a/src/components/games/setup/LogViewer.svelte +++ b/src/components/games/setup/LogViewer.svelte @@ -21,6 +21,8 @@ class="bg-slate-900 px-4 max-h-60 overflow-y-scroll scrollbar" >

+ ...Last 250 Lines: +
{@html convertLogColors($progressTracker.logs)}

diff --git a/src/lib/rpc/binaries.ts b/src/lib/rpc/binaries.ts index 3928dab..4aded78 100644 --- a/src/lib/rpc/binaries.ts +++ b/src/lib/rpc/binaries.ts @@ -24,6 +24,18 @@ export async function updateDataDirectory( } } +export async function getEndOfLogs(): Promise { + try { + return await invoke("get_end_of_logs", {}); + } catch (e) { + console.log( + "[OG]: Unexpected error encountered when updating data directory", + e + ); + return ""; + } +} + export async function extractAndValidateISO( pathToIso: string, gameName: string @@ -47,12 +59,14 @@ export async function extractAndValidateISO( export async function runDecompiler( pathToIso: string, - gameName: string + gameName: string, + truncateLogs: boolean = false ): Promise { try { return await invoke("run_decompiler", { pathToIso: pathToIso, gameName: gameName, + truncateLogs: truncateLogs, }); } catch (e) { console.log( @@ -68,12 +82,14 @@ export async function runDecompiler( export async function runCompiler( pathToIso: string, - gameName: string + gameName: string, + truncateLogs: boolean = false ): Promise { try { return await invoke("run_compiler", { pathToIso: pathToIso, gameName: gameName, + truncateLogs: truncateLogs, }); } catch (e) { console.log(