mirror of
https://github.com/open-goal/launcher.git
synced 2024-10-19 14:47:36 -04:00
Update logging and a few other small cleanup areas (#110)
This commit is contained in:
parent
a26d4531b7
commit
00f6218015
1
.github/workflows/build.yaml
vendored
1
.github/workflows/build.yaml
vendored
|
@ -45,6 +45,7 @@ jobs:
|
|||
ls ./src-tauri/data/
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
|
|
1
.github/workflows/release.yaml
vendored
1
.github/workflows/release.yaml
vendored
|
@ -94,6 +94,7 @@ jobs:
|
|||
ls ./src-tauri/data
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
import { isInDebugMode } from "$lib/setup/setup";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
import { isInstalling } from "./lib/stores/AppStore";
|
||||
import { loadTranslations } from "$lib/translations/translations";
|
||||
import { log } from "$lib/utils/log";
|
||||
|
||||
let revokeSpecificActions = false;
|
||||
|
||||
|
@ -58,7 +58,7 @@ import { loadTranslations } from "$lib/translations/translations";
|
|||
// Shift+Ctrl F12
|
||||
if (e.code == "F12" && e.ctrlKey && e.shiftKey) {
|
||||
revokeSpecificActions = false;
|
||||
console.log("hello world");
|
||||
log.info("Hello World - Dev Tools Enabled!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,25 +1,41 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import logoJak1 from "$assets/images/jak-tpl.webp";
|
||||
import logoJak2 from "$assets/images/jak-2.webp";
|
||||
import logoJak3 from "$assets/images/jak-3.webp";
|
||||
import { Link } from "svelte-navigator";
|
||||
import { link } from "svelte-navigator";
|
||||
import { useLocation } from "svelte-navigator";
|
||||
|
||||
let location = useLocation();
|
||||
</script>
|
||||
|
||||
<nav id="sidebar">
|
||||
<div class="games">
|
||||
<div class="jak-1 nav-item active">
|
||||
<Link to="/jak1" data-tooltip="Jak & Daxter: The Precursor Legacy">
|
||||
<div
|
||||
class="jak-1 nav-item"
|
||||
class:active={["/", "/jak1"].includes($location.pathname)}
|
||||
>
|
||||
<a
|
||||
href="/jak1"
|
||||
use:link
|
||||
data-tooltip="Jak & Daxter: The Precursor Legacy"
|
||||
>
|
||||
<img src={logoJak1} alt="Jak - TPL" />
|
||||
</Link>
|
||||
</a>
|
||||
</div>
|
||||
<div class="jak-2 nav-item disabled">
|
||||
<div
|
||||
class="jak-2 nav-item disabled"
|
||||
class:active={["/jak2"].includes($location.pathname)}
|
||||
>
|
||||
<!-- <Link to="no" data-tooltip="Jak 2"> -->
|
||||
<img src={logoJak2} alt="Jak 2" />
|
||||
<img src={logoJak2} alt="Jak 2" />
|
||||
<!-- </Link> -->
|
||||
</div>
|
||||
<div class="jak-3 nav-item disabled">
|
||||
<div
|
||||
class="jak-3 nav-item disabled"
|
||||
class:active={["/jak3"].includes($location.pathname)}
|
||||
>
|
||||
<!-- <Link to="no" data-tooltip="Jak 3"> -->
|
||||
<img src={logoJak3} alt="Jak 3" />
|
||||
<img src={logoJak3} alt="Jak 3" />
|
||||
<!-- </Link> -->
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,86 +46,101 @@
|
|||
<i class="bi bi-terminal-fill" />
|
||||
</Link>
|
||||
</div> -->
|
||||
<div class="settings nav-item">
|
||||
<Link to="settings" data-tooltip="Settings">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
</Link>
|
||||
<div
|
||||
class="settings nav-item"
|
||||
class:active={["/settings"].includes($location.pathname)}
|
||||
>
|
||||
<a href="/settings" use:link data-tooltip="Settings">
|
||||
<i class="fa-solid fa-gear" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
#sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: var(--bg-blue);
|
||||
opacity: 75%;
|
||||
transition: 500ms ease;
|
||||
width: 10vw;
|
||||
max-width: 10vw;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: var(--bg-blue);
|
||||
opacity: 75%;
|
||||
transition: 500ms ease;
|
||||
width: 10vw;
|
||||
max-width: 10vw;
|
||||
}
|
||||
|
||||
#sidebar:hover {
|
||||
opacity: 100%;
|
||||
}
|
||||
#sidebar:hover {
|
||||
opacity: 100%;
|
||||
}
|
||||
|
||||
.games {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
height: 50vh;
|
||||
}
|
||||
.games {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin: 0 5px;
|
||||
filter: grayscale(100%);
|
||||
transition: 500ms ease;
|
||||
opacity: 50%;
|
||||
}
|
||||
.nav-item {
|
||||
margin: 0 5px;
|
||||
filter: grayscale(100%);
|
||||
transition: 500ms ease;
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
filter: grayscale(0%);
|
||||
}
|
||||
.nav-item.active {
|
||||
filter: grayscale(0%);
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
filter: grayscale(0%);
|
||||
opacity: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.nav-item:hover {
|
||||
filter: grayscale(0%);
|
||||
opacity: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-item.disabled:hover,
|
||||
.nav-item.disabled {
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
.nav-item.disabled:hover,
|
||||
.nav-item.disabled {
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.nav-item a::before,
|
||||
.nav-item a::after {
|
||||
--scale: 0;
|
||||
position: absolute;
|
||||
transform: translateX(55px) scale(var(--scale));
|
||||
transition: 200ms ease;
|
||||
transform-origin: left center;
|
||||
}
|
||||
.nav-item a::before,
|
||||
.nav-item a::after {
|
||||
--scale: 0;
|
||||
position: absolute;
|
||||
transform: translateX(55px) scale(var(--scale));
|
||||
transition: 200ms ease;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
.nav-item a::before {
|
||||
padding: 0.5rem;
|
||||
width: max-content;
|
||||
background: #333;
|
||||
color: white;
|
||||
content: attr(data-tooltip);
|
||||
border-radius: 0.3rem;
|
||||
text-align: center;
|
||||
}
|
||||
.nav-item a::before {
|
||||
padding: 0.5rem;
|
||||
width: max-content;
|
||||
background: #333;
|
||||
color: white;
|
||||
content: attr(data-tooltip);
|
||||
border-radius: 0.3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-item a:hover::before {
|
||||
--scale: 1;
|
||||
}
|
||||
.nav-item a:hover::before {
|
||||
--scale: 1;
|
||||
}
|
||||
|
||||
.nav-item img {
|
||||
width: 100%;
|
||||
}
|
||||
.nav-item img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-bottom: 2em;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.settings.active i {
|
||||
color: #f18c31;
|
||||
}
|
||||
|
||||
.settings:hover i {
|
||||
color: #f18c31;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { appDir, join } from "@tauri-apps/api/path";
|
|||
import { Store } from "tauri-plugin-store-api";
|
||||
import { SupportedGame } from "./constants";
|
||||
import { fileExists } from "./utils/file";
|
||||
import { log } from "./utils/log";
|
||||
|
||||
class GameConfig {
|
||||
isInstalled: boolean = false;
|
||||
|
@ -54,13 +55,13 @@ export async function initConfig() {
|
|||
const path = await join(await appDir(), "settings.json");
|
||||
let configExists = await fileExists(path);
|
||||
if (!configExists) {
|
||||
console.log("[Launcher]: Settings file not found or could not be loaded!");
|
||||
log.info("settings file not found or could not be loaded!");
|
||||
await createDir(await appDir(), { recursive: true });
|
||||
await writeFile({
|
||||
contents: JSON.stringify(new LauncherConfig(), null, 2),
|
||||
path: path,
|
||||
});
|
||||
console.log("[Launcher]: Settings file initialized");
|
||||
log.info("settings file initialized");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +147,7 @@ export async function isAVXRequirementMet(): Promise<boolean> {
|
|||
await store.load();
|
||||
let requirements = await store.get("requirements");
|
||||
if (!requirements["avx"]) {
|
||||
console.log("Unsupported AVX");
|
||||
log.error("requirement false - AVX unsupported");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -156,7 +157,7 @@ export async function isOpenGLRequirementMet(): Promise<boolean> {
|
|||
await store.load();
|
||||
let requirements = await store.get("requirements");
|
||||
if (!requirements["openGL"]) {
|
||||
console.log("Unsupported OpenGL");
|
||||
log.error("requirement false - OpenGL unsupported");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -203,8 +204,9 @@ export async function shouldUpdateGameInstall(
|
|||
return false;
|
||||
}
|
||||
|
||||
console.log("Tools version is different than install verison");
|
||||
console.log("Tools: ", toolsVersion);
|
||||
console.log("Installed: ", installVersion);
|
||||
log.warn("Tools version is different than install verison", {
|
||||
tools: toolsVersion,
|
||||
installed: installVersion,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { log } from "$lib/utils/log";
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
|
||||
export async function getHighestSimd(): Promise<string> {
|
||||
|
@ -16,7 +17,7 @@ export async function openDir(dir: string): Promise<void> {
|
|||
try {
|
||||
return await invoke("open_dir", { dir });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +25,7 @@ export async function closeSplashScreen() {
|
|||
try {
|
||||
invoke("close_splashscreen");
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +33,6 @@ export async function copyDirectory(source: string, destination: string) {
|
|||
try {
|
||||
return await invoke("copy_dir", { dirSrc: source, dirDest: destination });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,20 +3,15 @@ import { appDir, join } from "@tauri-apps/api/path";
|
|||
import { os } from "@tauri-apps/api";
|
||||
import { getHighestSimd } from "$lib/rpc/commands";
|
||||
import { InstallStatus, isInstalling } from "../stores/AppStore";
|
||||
import { SETUP_SUCCESS, SupportedGame } from "$lib/constants";
|
||||
import {
|
||||
appendToInstallErrorLog,
|
||||
appendToInstallLog,
|
||||
clearInstallLogs,
|
||||
filePrompt,
|
||||
} from "$lib/utils/file";
|
||||
import { SETUP_ERROR, SETUP_SUCCESS, SupportedGame } from "$lib/constants";
|
||||
import { filePrompt } from "$lib/utils/file";
|
||||
import {
|
||||
setGameInstallVersion,
|
||||
setInstallStatus,
|
||||
setRequirementsMet,
|
||||
} from "../config";
|
||||
import { BaseDirectory, copyFile } from "@tauri-apps/api/fs";
|
||||
import { resolveErrorCode } from "./setup_errors";
|
||||
import { installLog, log } from "$lib/utils/log";
|
||||
|
||||
let sidecarOptions = {};
|
||||
|
||||
|
@ -51,6 +46,11 @@ export async function isOpenGLVersionSupported(
|
|||
if (output.code === 0) {
|
||||
return true;
|
||||
}
|
||||
log.error("opengl requirement check failed", {
|
||||
statusCode: output.code,
|
||||
stdout: output.stdout,
|
||||
stderr: output.stderr,
|
||||
});
|
||||
throw new Error("UNSUPPORTED OPENGL VERSION");
|
||||
}
|
||||
|
||||
|
@ -65,12 +65,6 @@ export async function checkRequirements(): Promise<Boolean> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function saveISO(filePath: string): Promise<any> {
|
||||
const appDirPath = await appDir();
|
||||
await copyFile(filePath, `${appDirPath}/jak.iso`, { dir: BaseDirectory.App });
|
||||
return;
|
||||
}
|
||||
|
||||
async function handleErrorCode(code: number, stepName: string) {
|
||||
isInstalling.update(() => false);
|
||||
const explaination = await resolveErrorCode(code);
|
||||
|
@ -80,6 +74,17 @@ async function handleErrorCode(code: number, stepName: string) {
|
|||
throw new Error(explaination);
|
||||
}
|
||||
|
||||
async function isoPrompt(): Promise<string> {
|
||||
InstallStatus.update(() => SETUP_SUCCESS.awaitingISO);
|
||||
const path = await filePrompt(["ISO", "iso"], "Jak ISO File");
|
||||
if (path === null) {
|
||||
InstallStatus.update(() => SETUP_ERROR.noISO);
|
||||
throw new Error("No ISO File Selected!");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} filePath
|
||||
* @returns {Promise<Boolean>}
|
||||
|
@ -101,10 +106,14 @@ export async function extractAndValidateISO(
|
|||
|
||||
const output = await command.execute();
|
||||
if (output.stdout) {
|
||||
await appendToInstallLog(SupportedGame.Jak1, output.stdout);
|
||||
installLog.info(output.stdout, {
|
||||
game: SupportedGame.Jak1,
|
||||
});
|
||||
}
|
||||
if (output.stderr) {
|
||||
await appendToInstallErrorLog(SupportedGame.Jak1, output.stdout);
|
||||
installLog.error(output.stderr, {
|
||||
game: SupportedGame.Jak1,
|
||||
});
|
||||
}
|
||||
if (output.code === 0) {
|
||||
return true;
|
||||
|
@ -131,10 +140,14 @@ export async function decompileGameData(filePath: string): Promise<boolean> {
|
|||
|
||||
const output = await command.execute();
|
||||
if (output.stdout) {
|
||||
await appendToInstallLog(SupportedGame.Jak1, output.stdout);
|
||||
installLog.info(output.stdout, {
|
||||
game: SupportedGame.Jak1,
|
||||
});
|
||||
}
|
||||
if (output.stderr) {
|
||||
await appendToInstallErrorLog(SupportedGame.Jak1, output.stdout);
|
||||
installLog.error(output.stderr, {
|
||||
game: SupportedGame.Jak1,
|
||||
});
|
||||
}
|
||||
if (output.code === 0) {
|
||||
return true;
|
||||
|
@ -159,10 +172,14 @@ export async function compileGame(filePath: string): Promise<Boolean> {
|
|||
|
||||
const output = await command.execute();
|
||||
if (output.stdout) {
|
||||
await appendToInstallLog(SupportedGame.Jak1, output.stdout);
|
||||
installLog.info(output.stdout, {
|
||||
game: SupportedGame.Jak1,
|
||||
});
|
||||
}
|
||||
if (output.stderr) {
|
||||
await appendToInstallErrorLog(SupportedGame.Jak1, output.stdout);
|
||||
installLog.error(output.stderr, {
|
||||
game: SupportedGame.Jak1,
|
||||
});
|
||||
}
|
||||
if (output.code === 0) {
|
||||
InstallStatus.update(() => SETUP_SUCCESS.ready);
|
||||
|
@ -175,8 +192,7 @@ export async function fullInstallation(game: SupportedGame): Promise<boolean> {
|
|||
let isoPath: string | string[];
|
||||
isInstalling.update(() => true);
|
||||
try {
|
||||
await clearInstallLogs(game);
|
||||
isoPath = await filePrompt();
|
||||
isoPath = await isoPrompt();
|
||||
await extractAndValidateISO(isoPath);
|
||||
await decompileGameData(isoPath);
|
||||
await compileGame(isoPath);
|
||||
|
@ -185,7 +201,9 @@ export async function fullInstallation(game: SupportedGame): Promise<boolean> {
|
|||
await setGameInstallVersion(game);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(`[OG]: Error encountered - ${err}`);
|
||||
installLog.error("unexpected error encountered", {
|
||||
error: err,
|
||||
});
|
||||
let errStatus = {
|
||||
status: err,
|
||||
percent: undefined,
|
||||
|
@ -204,7 +222,6 @@ export async function recompileGame(game: SupportedGame) {
|
|||
// TODO - probably should check the dir exists
|
||||
isInstalling.update(() => true);
|
||||
try {
|
||||
await clearInstallLogs(game);
|
||||
// decompile & compile game
|
||||
await decompileGameData(isoPath);
|
||||
await compileGame(isoPath);
|
||||
|
@ -212,7 +229,9 @@ export async function recompileGame(game: SupportedGame) {
|
|||
await setGameInstallVersion(game);
|
||||
isInstalling.update(() => false);
|
||||
} catch (err) {
|
||||
console.log(`[OG]: Error encountered - ${err}`);
|
||||
installLog.error("unexpected error encountered", {
|
||||
error: err,
|
||||
});
|
||||
let errStatus = {
|
||||
status: err,
|
||||
percent: undefined,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { fileExists } from "../utils/file";
|
||||
import { appDir, join } from "@tauri-apps/api/path";
|
||||
import { readTextFile } from "@tauri-apps/api/fs";
|
||||
import { log } from "$lib/utils/log";
|
||||
|
||||
interface ErrorCodeMetadataEntry {
|
||||
msg: string;
|
||||
|
@ -20,9 +21,9 @@ export async function resolveErrorCode(
|
|||
"error-code-metadata.json"
|
||||
);
|
||||
if (!(await fileExists(errorMetadataPath))) {
|
||||
console.log(
|
||||
`[OG]: Could not locate error metadata file at ${errorMetadataPath}`
|
||||
);
|
||||
log.warn("could not locate error metadata file at path", {
|
||||
path: errorMetadataPath,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
const jsonData = JSON.parse(await readTextFile(errorMetadataPath));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Convert } from "./translation_schema";
|
||||
import type { TranslationSchema } from "./translation_schema";
|
||||
import english from "$assets/translations/english.json";
|
||||
import { log } from "$lib/utils/log";
|
||||
|
||||
let supportedTranslations = ["english"];
|
||||
|
||||
|
@ -8,10 +9,12 @@ export let TranslatedStrings: TranslationSchema;
|
|||
|
||||
export function loadTranslations(language: string) {
|
||||
if (!supportedTranslations.includes(language)) {
|
||||
console.log("Language not supported!");
|
||||
log.error("Language not supported!", {
|
||||
language: language,
|
||||
});
|
||||
}
|
||||
// TODO - would prefer to import this by a raw path but have to import
|
||||
// for vite reasons -- maybe there is a different way?
|
||||
// for vite reasons -- maybe there is a different way to ensure they are bundled?
|
||||
if (language === "english") {
|
||||
TranslatedStrings = Convert.toTranslationSchema(JSON.stringify(english));
|
||||
}
|
||||
|
|
52
src/lib/utils/data-files.ts
Normal file
52
src/lib/utils/data-files.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { copyDirectory } from "$lib/rpc/commands";
|
||||
import { readTextFile } from "@tauri-apps/api/fs";
|
||||
import { join, appDir, resourceDir } from "@tauri-apps/api/path";
|
||||
import { dirExists, fileExists } from "./file";
|
||||
import { log } from "./log";
|
||||
|
||||
export async function dataDirectoryExists(): Promise<boolean> {
|
||||
return await dirExists(await join(await appDir(), "data"));
|
||||
}
|
||||
|
||||
export async function isDataDirectoryUpToDate(): Promise<boolean> {
|
||||
const resourceDirPath = await resourceDir();
|
||||
const appDirPath = await appDir();
|
||||
// There should be a `metadata.json` which will help us know if the directory is out of date
|
||||
// aka, does the app have updated files compared to what the user has in their appDir.
|
||||
const userMetaPath = await join(appDirPath, "data", "metadata.json");
|
||||
const appMetaPath = await join(resourceDirPath, "data", "metadata.json");
|
||||
if (!(await fileExists(userMetaPath))) {
|
||||
log.warn("couldn't locate user's metadata file at path", {
|
||||
path: userMetaPath,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
// If it's there, read it in and check the version, compare with the app's
|
||||
const userMetaVersion = JSON.parse(await readTextFile(userMetaPath)).version;
|
||||
const appMetaVersion = JSON.parse(await readTextFile(appMetaPath)).version;
|
||||
if (userMetaVersion != appMetaVersion) {
|
||||
log.warn("user version does not match app version", {
|
||||
userVersion: userMetaPath,
|
||||
appVersion: appMetaVersion,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
// NOTE - the user can of course mess up their directory more, but we can only hold their hands so much
|
||||
// TODO - better to add some sort of "verify local data" feature in the app imo
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function copyDataDirectory(): Promise<boolean> {
|
||||
const resourceDirPath = await resourceDir();
|
||||
const appDirPath = await appDir();
|
||||
|
||||
let src = `${resourceDirPath.replaceAll("\\\\?\\", "")}data`;
|
||||
let dst = `${appDirPath}data`;
|
||||
|
||||
try {
|
||||
await copyDirectory(src, dst);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,22 +1,7 @@
|
|||
import { copyDirectory } from "$lib/rpc/commands";
|
||||
import { SETUP_SUCCESS, SETUP_ERROR, SupportedGame } from "$lib/constants";
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
import { SETUP_SUCCESS, SETUP_ERROR } from "$lib/constants";
|
||||
import { open } from "@tauri-apps/api/dialog";
|
||||
import {
|
||||
createDir,
|
||||
readDir,
|
||||
readTextFile,
|
||||
writeFile,
|
||||
} from "@tauri-apps/api/fs";
|
||||
import {
|
||||
appDir,
|
||||
dataDir,
|
||||
dirname,
|
||||
join,
|
||||
logDir,
|
||||
resourceDir,
|
||||
} from "@tauri-apps/api/path";
|
||||
import { InstallStatus, ProcessLogs } from "../stores/AppStore";
|
||||
import { readDir, readTextFile } from "@tauri-apps/api/fs";
|
||||
import { InstallStatus } from "../stores/AppStore";
|
||||
|
||||
export async function fileExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
|
@ -37,123 +22,19 @@ export async function dirExists(path: string): Promise<boolean> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function filePrompt(): Promise<string> {
|
||||
// TODO - shouldn't be ISO specific in this function
|
||||
// TODO - pull strings out into args
|
||||
InstallStatus.update(() => SETUP_SUCCESS.awaitingISO);
|
||||
export async function filePrompt(
|
||||
extensions: string[],
|
||||
name: string
|
||||
): Promise<string | null> {
|
||||
const path = await open({
|
||||
multiple: false,
|
||||
directory: false,
|
||||
filters: [{ extensions: ["ISO", "iso"], name: "Jak ISO File" }],
|
||||
filters: [{ extensions: extensions, name: name }],
|
||||
});
|
||||
|
||||
if (Array.isArray(path) || path === null) {
|
||||
InstallStatus.update(() => SETUP_ERROR.noISO);
|
||||
throw new Error("No ISO File Selected!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// TODO - move this stuff into a separate file, this isn't generic file util stuff anymore
|
||||
|
||||
export async function dataDirectoryExists(): Promise<boolean> {
|
||||
return await dirExists(await join(await appDir(), "data"));
|
||||
}
|
||||
|
||||
export async function isDataDirectoryUpToDate(): Promise<boolean> {
|
||||
const resourceDirPath = await resourceDir();
|
||||
const appDirPath = await appDir();
|
||||
// There should be a `metadata.json` which will help us know if the directory is out of date
|
||||
// aka, does the app have updated files compared to what the user has in their appDir.
|
||||
const userMetaPath = await join(appDirPath, "data", "metadata.json");
|
||||
const appMetaPath = await join(resourceDirPath, "data", "metadata.json");
|
||||
if (!(await fileExists(userMetaPath))) {
|
||||
console.log(
|
||||
`[Launcher]: Couldn't locate user's metadata file at '${userMetaPath}'`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// If it's there, read it in and check the version, compare with the app's
|
||||
const userMetaVersion = JSON.parse(await readTextFile(userMetaPath)).version;
|
||||
const appMetaVersion = JSON.parse(await readTextFile(appMetaPath)).version;
|
||||
if (userMetaVersion != appMetaVersion) {
|
||||
console.log(
|
||||
`[Launcher]: User version ${userMetaVersion} does not match app version ${appMetaVersion}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// NOTE - the user can of course mess up their directory more, but we can only hold their hands so much
|
||||
// TODO - better to add some sort of "verify local data" feature in the app imo
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function copyDataDirectory(): Promise<boolean> {
|
||||
const resourceDirPath = await resourceDir();
|
||||
const appDirPath = await appDir();
|
||||
|
||||
let src = `${resourceDirPath.replaceAll("\\\\?\\", "")}data`;
|
||||
let dst = `${appDirPath}data`;
|
||||
|
||||
try {
|
||||
await copyDirectory(src, dst);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - move this to a logging file and replace all `console.logs` in the entire project
|
||||
|
||||
export async function clearInstallLogs(supportedGame: SupportedGame) {
|
||||
ProcessLogs.set(null);
|
||||
const dir = await logDir();
|
||||
let fileName = `${supportedGame}-install.log`;
|
||||
let fullPath = await join(dir, fileName);
|
||||
if (await fileExists(fullPath)) {
|
||||
await writeFile({ contents: "", path: fullPath });
|
||||
}
|
||||
fileName = `${supportedGame}-install-errors.log`;
|
||||
fullPath = await join(dir, fileName);
|
||||
if (await fileExists(fullPath)) {
|
||||
await writeFile({ contents: "", path: fullPath });
|
||||
}
|
||||
}
|
||||
|
||||
export async function appendToInstallLog(
|
||||
supportedGame: SupportedGame,
|
||||
text: string
|
||||
) {
|
||||
const dir = await logDir();
|
||||
const fileName = `${supportedGame}-install.log`;
|
||||
const fullPath = await join(dir, fileName);
|
||||
console.log(`[OG]: Writing logs to ${fullPath}`);
|
||||
let contents: string;
|
||||
if (!(await fileExists(fullPath))) {
|
||||
await createDir(await dirname(fullPath), { recursive: true });
|
||||
} else {
|
||||
contents = await readTextFile(fullPath);
|
||||
}
|
||||
contents += text;
|
||||
ProcessLogs.update(() => contents);
|
||||
await writeFile({ contents: contents, path: fullPath });
|
||||
}
|
||||
|
||||
export async function appendToInstallErrorLog(
|
||||
supportedGame: SupportedGame,
|
||||
text: string
|
||||
) {
|
||||
const dir = await logDir();
|
||||
const fileName = `${supportedGame}-install-errors.log`;
|
||||
const fullPath = await join(dir, fileName);
|
||||
console.log(`[OG]: Writing logs to ${fullPath}`);
|
||||
let contents: string;
|
||||
if (!(await fileExists(fullPath))) {
|
||||
await createDir(await dirname(fullPath), { recursive: true });
|
||||
} else {
|
||||
contents = await readTextFile(fullPath);
|
||||
}
|
||||
contents += text;
|
||||
ProcessLogs.update(() => contents);
|
||||
await writeFile({ contents: contents, path: fullPath });
|
||||
}
|
||||
|
|
173
src/lib/utils/log.ts
Normal file
173
src/lib/utils/log.ts
Normal file
|
@ -0,0 +1,173 @@
|
|||
import {
|
||||
writeFile,
|
||||
createDir,
|
||||
readTextFile,
|
||||
readDir,
|
||||
} from "@tauri-apps/api/fs";
|
||||
import { logDir, join, dirname, basename } from "@tauri-apps/api/path";
|
||||
import { fileExists } from "./file";
|
||||
|
||||
enum LogLevel {
|
||||
Debug = "debug",
|
||||
Info = "info",
|
||||
Warn = "warn",
|
||||
Error = "error",
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
metadata: Object = {};
|
||||
level: LogLevel = LogLevel.Debug;
|
||||
fileNamePrefix: string;
|
||||
|
||||
logFileName: string = undefined;
|
||||
maxFileRotate: number = 10;
|
||||
buffer: string[] = [];
|
||||
lastFlush: Date = undefined;
|
||||
flushMillisecondInterval: number = 2500;
|
||||
|
||||
constructor(metadata: Object, level: LogLevel, fileNamePrefix: string) {
|
||||
this.metadata = metadata;
|
||||
this.level = level;
|
||||
this.fileNamePrefix = fileNamePrefix;
|
||||
}
|
||||
|
||||
private logMessage(msg: string, level: LogLevel, meta?: Object): string {
|
||||
let val = this.metadata;
|
||||
// Apply provided metadata
|
||||
if (meta !== undefined) {
|
||||
for (const [key, value] of Object.entries(meta)) {
|
||||
val[key] = `${value}`;
|
||||
}
|
||||
}
|
||||
val["level"] = level;
|
||||
val["message"] = msg;
|
||||
val["timestamp"] = new Date().toISOString();
|
||||
return JSON.stringify(val);
|
||||
}
|
||||
|
||||
private async flushToFile(logData: string) {
|
||||
this.buffer.push(logData);
|
||||
// Flush if we havn't in X amount of time
|
||||
if (
|
||||
this.buffer.length > 0 &&
|
||||
(this.lastFlush === undefined ||
|
||||
this.lastFlush.valueOf() >
|
||||
new Date().valueOf() + this.flushMillisecondInterval)
|
||||
) {
|
||||
await this.writeToFile();
|
||||
this.lastFlush = new Date();
|
||||
this.buffer = [];
|
||||
}
|
||||
}
|
||||
|
||||
private async writeToFile() {
|
||||
// If we havn't figured out our log file name yet, figure it out now
|
||||
// This is so we don't have to clear our logs manually, just rotate files
|
||||
if (this.logFileName === undefined) {
|
||||
this.logFileName = await this.rotateLogFile();
|
||||
}
|
||||
|
||||
// Check if the file exists and read it's contents if so
|
||||
const dir = await logDir();
|
||||
const fullPath = await join(dir, this.logFileName);
|
||||
const logExists = await fileExists(fullPath);
|
||||
let contents = "";
|
||||
if (logExists) {
|
||||
contents = await readTextFile(fullPath);
|
||||
if (contents === null || contents === undefined) {
|
||||
contents = "";
|
||||
}
|
||||
} else {
|
||||
await createDir(await dirname(fullPath), { recursive: true });
|
||||
}
|
||||
|
||||
// Build up the string to append and write it
|
||||
this.buffer.forEach((data) => {
|
||||
if (data !== undefined && data !== null) {
|
||||
contents += data + "\n";
|
||||
}
|
||||
});
|
||||
await writeFile({ contents: contents, path: fullPath });
|
||||
}
|
||||
|
||||
private async rotateLogFile(): Promise<string> {
|
||||
const dir = await logDir();
|
||||
const logFiles = await readDir(dir);
|
||||
let numLogs = 0;
|
||||
let oldestLogIndex = 0;
|
||||
for (let i = 0; i < logFiles.length; i++) {
|
||||
const logFile = logFiles[i];
|
||||
const logFileName = await basename(logFile.path);
|
||||
// prefix_number.log
|
||||
const [prefix, number] = logFileName.split("_");
|
||||
if (prefix === this.fileNamePrefix) {
|
||||
numLogs++;
|
||||
oldestLogIndex = Math.min(oldestLogIndex, parseInt(number));
|
||||
}
|
||||
}
|
||||
if (numLogs > this.maxFileRotate) {
|
||||
return `${this.fileNamePrefix}_${oldestLogIndex}.log`;
|
||||
} else {
|
||||
return `${this.fileNamePrefix}_${numLogs}.log`;
|
||||
}
|
||||
}
|
||||
|
||||
child(meta: Object): Logger {
|
||||
let newLogger = new Logger(this.metadata, this.level, this.fileNamePrefix);
|
||||
let newMeta = this.metadata;
|
||||
for (const [key, value] of Object.entries(meta)) {
|
||||
newMeta[key] = `${value}`;
|
||||
}
|
||||
newLogger.metadata = newMeta;
|
||||
newLogger.logFileName = this.logFileName;
|
||||
return newLogger;
|
||||
}
|
||||
|
||||
debug(msg: string, meta?: Object) {
|
||||
if (this.level <= LogLevel.Debug) {
|
||||
const logData = this.logMessage(msg, LogLevel.Debug, meta);
|
||||
console.log(logData);
|
||||
this.flushToFile(logData);
|
||||
}
|
||||
}
|
||||
|
||||
info(msg: string, meta?: Object) {
|
||||
if (this.level <= LogLevel.Info) {
|
||||
const logData = this.logMessage(msg, LogLevel.Info, meta);
|
||||
console.log(logData);
|
||||
this.flushToFile(logData);
|
||||
}
|
||||
}
|
||||
|
||||
warn(msg: string, meta?: Object) {
|
||||
if (this.level <= LogLevel.Warn) {
|
||||
const logData = this.logMessage(msg, LogLevel.Warn, meta);
|
||||
console.warn(logData);
|
||||
this.flushToFile(logData);
|
||||
}
|
||||
}
|
||||
|
||||
error(msg: string, meta?: Object) {
|
||||
if (this.level <= LogLevel.Error) {
|
||||
const logData = this.logMessage(msg, LogLevel.Error, meta);
|
||||
console.error(logData);
|
||||
this.flushToFile(logData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const log = new Logger(
|
||||
{
|
||||
name: "launcher",
|
||||
},
|
||||
LogLevel.Debug,
|
||||
"launcher"
|
||||
);
|
||||
|
||||
export const installLog = new Logger(
|
||||
{
|
||||
name: "launcher-install",
|
||||
},
|
||||
LogLevel.Debug,
|
||||
"launcher-install"
|
||||
);
|
|
@ -6,8 +6,11 @@
|
|||
import GameContent from "../components/games/GameControls.svelte";
|
||||
import GameSetup from "../components/games/setup/GameSetup.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { copyDataDirectory, isDataDirectoryUpToDate } from "$lib/utils/file";
|
||||
import { gameNeedsReinstall } from "$lib/stores/AppStore";
|
||||
import {
|
||||
copyDataDirectory,
|
||||
isDataDirectoryUpToDate,
|
||||
} from "$lib/utils/data-files";
|
||||
|
||||
const params = useParams();
|
||||
let activeGame = SupportedGame.Jak1;
|
||||
|
@ -85,7 +88,7 @@
|
|||
{errorText}
|
||||
{/if}
|
||||
{:else}
|
||||
<GameContent {activeGame} on:change={updateGameState}/>
|
||||
<GameContent {activeGame} on:change={updateGameState} />
|
||||
{/if}
|
||||
{:else}
|
||||
{#if $gameNeedsReinstall}
|
||||
|
@ -97,5 +100,5 @@
|
|||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<!-- TODO - component library - spinner -->
|
||||
<!-- TODO - component library - spinner -->
|
||||
{/if}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
import { checkRequirements } from "$lib/setup/setup";
|
||||
import { onMount } from "svelte";
|
||||
import logo from "$assets/images/icon.webp";
|
||||
import { copyDataDirectory, dataDirectoryExists } from "$lib/utils/file";
|
||||
import { copyDataDirectory, dataDirectoryExists } from "$lib/utils/data-files";
|
||||
import { log } from "$lib/utils/log";
|
||||
|
||||
let currentProgress = 0;
|
||||
let currentStatusText = "Initializing Config";
|
||||
|
@ -29,7 +30,9 @@
|
|||
currentProgress = 50;
|
||||
await copyDataDirectory();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
log.error("error encountered when copying data files", {
|
||||
error: err
|
||||
});
|
||||
currentStatusText = `Error - ${err}`;
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue