From 2cee5383765303eccae4585e5478c4ad07c9bded Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Fri, 22 Jul 2022 23:50:40 -0400 Subject: [PATCH] config: fix issue related to loading the settings on initial launch (#109) --- package.json | 3 +- src-tauri/Cargo.lock | 13 - src-tauri/Cargo.toml | 4 - src-tauri/src/main.rs | 2 - src/components/games/GameControls.svelte | 14 +- src/components/games/setup/GameSetup.svelte | 10 +- src/components/games/setup/Progress.svelte | 2 +- .../games/setup/Requirements.svelte | 6 +- src/lib/config.ts | 405 ++++++++++-------- src/lib/setup/setup.ts | 40 +- src/lib/stores/AppStore.ts | 2 +- src/routes/Game.svelte | 18 +- src/splash/Splash.svelte | 9 - 13 files changed, 282 insertions(+), 246 deletions(-) diff --git a/package.json b/package.json index 6d0df6b..fda51b8 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ }, "dependencies": { "@tauri-apps/api": "^1.0.2", - "svelte-navigator": "^3.1.6", - "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store" + "svelte-navigator": "^3.1.6" } } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 6978fac..b4fba3d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -61,7 +61,6 @@ dependencies = [ "serde_json", "tauri", "tauri-build", - "tauri-plugin-store", ] [[package]] @@ -2846,18 +2845,6 @@ dependencies = [ "tauri-utils", ] -[[package]] -name = "tauri-plugin-store" -version = "0.0.0" -source = "git+https://github.com/tauri-apps/tauri-plugin-store#17cf4d781c82a6c6578c472068c11136c0652d50" -dependencies = [ - "log", - "serde", - "serde_json", - "tauri", - "thiserror", -] - [[package]] name = "tauri-runtime" version = "0.10.2" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3e11f75..ab386cf 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,10 +19,6 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.0.3", features = ["api-all", "devtools"] } -[dependencies.tauri-plugin-store] -git = "https://github.com/tauri-apps/tauri-plugin-store" -#branch = "main" - [features] # by default Tauri runs in production mode # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 8f28a07..fb0e08a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,7 +4,6 @@ )] use tauri::RunEvent; -use tauri_plugin_store::PluginBuilder; mod commands; use commands::close_splashscreen; @@ -14,7 +13,6 @@ use commands::copy_dir; fn main() { tauri::Builder::default() - .plugin(PluginBuilder::default().build()) .invoke_handler(tauri::generate_handler![ get_highest_simd, open_dir, diff --git a/src/components/games/GameControls.svelte b/src/components/games/GameControls.svelte index 028ee5b..d24e81e 100644 --- a/src/components/games/GameControls.svelte +++ b/src/components/games/GameControls.svelte @@ -1,5 +1,5 @@ diff --git a/src/lib/config.ts b/src/lib/config.ts index 99f3f04..ade0d8c 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,212 +1,255 @@ import { createDir, readTextFile, writeFile } from "@tauri-apps/api/fs"; 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; - version: string = null; +interface Serializable { + deserialize(input: Object): T; } -// TODO: LINK REQUIREMENTS TO CHECK REQUIREMENTS FUNCTION TO AVOID RUNNING FUNCTION IF REQUIREMENTS ARE MET -class LauncherConfig { - version = "1.0"; - requirements: { avx: boolean | null; openGL: boolean | null } = { - avx: null, - openGL: null, - }; +class GameConfig implements Serializable { + isInstalled: boolean = false; + version: string = null; + + deserialize(json: JSON): GameConfig { + this.isInstalled = json["isInstalled"]; + this.version = json["version"]; + return this; + } +} + +class GameRequirements implements Serializable { + avx: boolean | null = null; + openGL: boolean | null = null; + + deserialize(json: JSON): GameRequirements { + this.avx = json["avx"]; + this.openGL = json["openGL"]; + return this; + } +} + +export class LauncherConfig implements Serializable { + version: string | null = "1.0"; + requirements: GameRequirements = new GameRequirements(); games = { [SupportedGame.Jak1]: new GameConfig(), [SupportedGame.Jak2]: new GameConfig(), [SupportedGame.Jak3]: new GameConfig(), [SupportedGame.JakX]: new GameConfig(), }; - lastActiveGame: SupportedGame; -} + lastActiveGame: SupportedGame | null; -const store = new Store("settings.json"); + private _loaded: boolean = false; -/** - * Checks the version to enable safe config operations - * @param {*} version "." - * @returns True if majors match, and expected minor greater than or equal to stored. False otherwise, or if no version can be found - */ -async function validVersion(version: string): Promise { - let [major, minor] = version.split("."); - await store.load(); - if (!(await store.has("version"))) { - return false; + deserialize(json: JSON): LauncherConfig { + this.version = json["version"]; + this.requirements = new GameRequirements().deserialize( + json["requirements"] + ); + this.games[SupportedGame.Jak1] = new GameConfig().deserialize( + json["games"][SupportedGame.Jak1] + ); + this.games[SupportedGame.Jak2] = new GameConfig().deserialize( + json["games"][SupportedGame.Jak2] + ); + this.games[SupportedGame.Jak3] = new GameConfig().deserialize( + json["games"][SupportedGame.Jak3] + ); + this.games[SupportedGame.JakX] = new GameConfig().deserialize( + json["games"][SupportedGame.JakX] + ); + this.lastActiveGame = json["lastActiveGame"]; + return this; } - let [storedMajor, storedMinor]: string[] = (await store.get("version")).split( - "." - ); - if (major != storedMajor) { - return false; - } - if (parseInt(minor) < parseInt(storedMinor)) { - return false; - } - return true; -} -export async function initConfig() { - const path = await join(await appDir(), "settings.json"); - let configExists = await fileExists(path); - if (!configExists) { - log.info("settings file not found or could not be loaded!"); - await createDir(await appDir(), { recursive: true }); + private async loadConfigFromFile() { + if (this._loaded) { + return; + } + const configPath = await join(await appDir(), "settings.json"); + const configExists = await fileExists(configPath); + if (!configExists) { + console.log( + `[Launcher]: Settings file not found at '${configPath}, initializing with defaults!` + ); + await createDir(await appDir(), { recursive: true }); + await writeFile({ + contents: JSON.stringify(this, null, 2), + path: configPath, + }); + console.log("[Launcher]: Settings file initialized"); + } else { + const data = await readTextFile(configPath); + this.deserialize(JSON.parse(data)); + } + this._loaded = true; + } + + private async saveConfigToFile() { + if (!this._loaded) { + log.info("config not loaded when trying to save, initializing it first!"); + await this.loadConfigFromFile(); + return; + } + const path = await join(await appDir(), "settings.json"); await writeFile({ - contents: JSON.stringify(new LauncherConfig(), null, 2), + contents: JSON.stringify(this, null, 2), path: path, }); log.info("settings file initialized"); } -} -/** - * If a game is installed or not - * @param {string} supportedGame - * @returns {Promise} - */ -export async function getInstallStatus( - supportedGame: SupportedGame -): Promise { - await store.load(); - if (!(await validVersion("1.0"))) { - return false; - } - // TODO: create a proper type for gameConfigs - exists with 'LauncherConfig' - const gameConfigs: object = await store.get("games"); - if (gameConfigs == null || !(supportedGame in gameConfigs)) { - return false; - } - return gameConfigs[supportedGame].isInstalled; -} - -/** - * The last game that was considered active in the launcher - * @returns {Promise} - */ -export async function getLastActiveGame(): Promise { - await store.load(); - if (!(await validVersion("1.0"))) { - return null; - } - - const lastActiveGame: SupportedGame = await store.get("lastActiveGame"); - return lastActiveGame; -} - -/** - * @param {string} supportedGame - * @param {boolean} installed - * @returns - */ -export async function setInstallStatus( - supportedGame: SupportedGame, - installed: boolean -): Promise { - await store.load(); - if (!(await validVersion("1.0"))) { - return; - } - // TODO: create a proper type for gameConfigs - 'LauncherConfig' - let gameConfigs: object = await store.get("games"); - // NOTE: Do we need this conditional? Considering we generate the store file this condition should never happen. - if (gameConfigs == null || !(supportedGame in gameConfigs)) { - return; - } - gameConfigs[supportedGame].isInstalled = installed; - await store.set("games", gameConfigs); - await store.save(); -} - -export async function setRequirementsMet( - avx: boolean = null, - openGL: boolean = null -) { - await store.load(); - await store.set("requirements", { avx, openGL }); - await store.save(); - return; -} - -/** - * Checks the user config file to see if avx and openGL requirements are met. - */ -export async function areRequirementsMet(): Promise { - if ((await isAVXRequirementMet()) && (await isOpenGLRequirementMet())) { + /** + * Checks the version to enable safe config operations + * + * Assumes the config has been loaded before calling to reduce boilerplate + * + * @param {*} version "." + * @returns True if majors match, and expected minor greater than or equal to stored. False otherwise, or if no version can be found + */ + private validVersion(requiredVersion: string): boolean { + const [requiredMajor, requiredMinor] = requiredVersion.split("."); + if (this.version === null) { + return false; + } + const [storedMajor, storedMinor] = this.version.split("."); + if (requiredMajor != storedMajor) { + return false; + } + if (parseInt(requiredMinor) < parseInt(storedMinor)) { + return false; + } return true; } - return false; -} -export async function isAVXRequirementMet(): Promise { - await store.load(); - let requirements = await store.get("requirements"); - if (!requirements["avx"]) { - log.error("requirement false - AVX unsupported"); - return false; - } - return true; -} + // GETTERS -export async function isOpenGLRequirementMet(): Promise { - await store.load(); - let requirements = await store.get("requirements"); - if (!requirements["openGL"]) { - log.error("requirement false - OpenGL unsupported"); - return false; - } - return true; -} - -export async function getGameInstallVersion( - game: SupportedGame -): Promise { - // TODO - this can fail on first time startup from splash (where the config is init) - // no idea why yet - await store.load(); - let games: GameConfig = await store.get("games"); - const { version } = games[game]; - return version; -} - -export async function setGameInstallVersion(game: SupportedGame) { - const version = await getLatestToolsVersion(); - await store.load(); - let games: GameConfig = await store.get("games"); - games[game].version = version; - await store.set("games", games); - return await store.save(); -} - -export async function getLatestToolsVersion(): Promise { - const appDirPath = await appDir(); - const userMetaPath = await join(appDirPath, "data", "metadata.json"); - const data = await readTextFile(userMetaPath); - const { version } = JSON.parse(data); - return version; -} - -export async function shouldUpdateGameInstall( - game: SupportedGame -): Promise { - const installVersion = await getGameInstallVersion(game); - if (installVersion === null || installVersion === undefined) { - return false; - } - const toolsVersion = await getLatestToolsVersion(); - - if (installVersion === toolsVersion) { - return false; + /** + * If a game is installed or not + * @param {string} supportedGame + * @returns {Promise} + */ + async getInstallStatus(supportedGame: SupportedGame): Promise { + await this.loadConfigFromFile(); + if (!this.validVersion("1.0")) { + return false; + } + const gameConfigs = this.games; + if (gameConfigs === null || !(supportedGame in gameConfigs)) { + return false; + } + return gameConfigs[supportedGame].isInstalled; } - log.warn("Tools version is different than install verison", { - tools: toolsVersion, - installed: installVersion, - }); - return true; + /** + * The last game that was considered active in the launcher + * @returns {Promise} + */ + async getLastActiveGame(): Promise { + await this.loadConfigFromFile(); + if (!this.validVersion("1.0")) { + return null; + } + return this.lastActiveGame; + } + + /** + * Checks the user config file to see if avx and openGL requirements are met. + */ + async areRequirementsMet(): Promise { + await this.loadConfigFromFile(); + if (!this.validVersion("1.0")) { + return false; + } + return this.requirements.avx && this.requirements.openGL; + } + + async isAVXRequirementMet(): Promise { + await this.loadConfigFromFile(); + if (!this.validVersion("1.0")) { + log.error("requirement false - AVX unsupported"); + return false; + } + return this.requirements.avx; + } + + async isOpenGLRequirementMet(): Promise { + await this.loadConfigFromFile(); + if (!this.validVersion("1.0")) { + log.error("requirement false - OpenGL 4.3 unsupported"); + return false; + } + return this.requirements.openGL; + } + + async getGameInstallVersion(game: SupportedGame): Promise { + await this.loadConfigFromFile(); + if (!this.validVersion("1.0")) { + return null; + } + return this.games[game].version; + } + + private async getLatestProjectBinaryVersion(): Promise { + // TODO - make a LauncherMetadata class similar to this + const appDirPath = await appDir(); + const userMetaPath = await join(appDirPath, "data", "metadata.json"); + // TODO - ensure it exists! + const data = await readTextFile(userMetaPath); + const { version } = JSON.parse(data); + return version; + } + + async shouldUpdateGameInstall(game: SupportedGame): Promise { + const installVersion = await this.getGameInstallVersion(game); + if (installVersion === null || installVersion === undefined) { + return false; + } + const toolsVersion = await this.getLatestProjectBinaryVersion(); + + if (installVersion === toolsVersion) { + return false; + } + + log.warn("Tools version is different than install verison", { + tools: toolsVersion, + installed: installVersion, + }); + return true; + } + + // SETTERS + + /** + * @param {string} supportedGame + * @param {boolean} installed + * @returns + */ + async setInstallStatus( + supportedGame: SupportedGame, + installed: boolean + ): Promise { + this.games[supportedGame].isInstalled = installed; + await this.saveConfigToFile(); + } + + async setRequirementsMet( + avx: boolean = null, + openGL: boolean = null + ): Promise { + this.requirements.avx = avx; + this.requirements.openGL = openGL; + await this.saveConfigToFile(); + } + + async setGameInstallVersion(game: SupportedGame) { + const version = await this.getLatestProjectBinaryVersion(); + this.games[game].version = version; + await this.saveConfigToFile(); + } } + +// Initialize with defaults +export let launcherConfig: LauncherConfig = new LauncherConfig(); diff --git a/src/lib/setup/setup.ts b/src/lib/setup/setup.ts index 8d1a3e0..6990745 100644 --- a/src/lib/setup/setup.ts +++ b/src/lib/setup/setup.ts @@ -5,13 +5,10 @@ import { getHighestSimd } from "$lib/rpc/commands"; import { InstallStatus, isInstalling } from "../stores/AppStore"; import { SETUP_ERROR, SETUP_SUCCESS, SupportedGame } from "$lib/constants"; import { filePrompt } from "$lib/utils/file"; -import { - setGameInstallVersion, - setInstallStatus, - setRequirementsMet, -} from "../config"; +import { launcherConfig } from "$lib/config"; import { resolveErrorCode } from "./setup_errors"; import { installLog, log } from "$lib/utils/log"; +import { ProcessLogs } from "$lib/stores/AppStore"; let sidecarOptions = {}; @@ -27,7 +24,7 @@ export async function isAVXSupported() { if (highestSIMD.toLowerCase().startsWith("avx")) { return true; } - throw new Error("UNSUPPORTED AVX"); + return false; } /** @@ -38,7 +35,8 @@ export async function isOpenGLVersionSupported( version: string ): Promise { if ((await os.platform()) === "darwin") { - throw new Error("Unsupported OS!"); + // TODO - log! + return false; } // Otherwise, query for the version let command = Command.sidecar("bin/glewinfo", ["-version", version]); @@ -47,21 +45,22 @@ export async function isOpenGLVersionSupported( return true; } log.error("opengl requirement check failed", { + version: version, statusCode: output.code, stdout: output.stdout, stderr: output.stderr, }); - throw new Error("UNSUPPORTED OPENGL VERSION"); + return false; } -export async function checkRequirements(): Promise { +export async function checkRequirements(): Promise { try { - await isAVXSupported(); - await isOpenGLVersionSupported("4.3"); - await setRequirementsMet(true, true); - return true; + const isAVX = await isAVXSupported(); + const isOpenGL = await isOpenGLVersionSupported("4.3"); + console.log(`avx - ${isAVX} opengl - ${isOpenGL}`); + await launcherConfig.setRequirementsMet(isAVX, isOpenGL); } catch (err) { - return false; + await launcherConfig.setRequirementsMet(false, false); } } @@ -109,11 +108,13 @@ export async function extractAndValidateISO( installLog.info(output.stdout, { game: SupportedGame.Jak1, }); + ProcessLogs.update((currLogs) => currLogs + output.stdout); } if (output.stderr) { installLog.error(output.stderr, { game: SupportedGame.Jak1, }); + ProcessLogs.update((currLogs) => currLogs + output.stderr); } if (output.code === 0) { return true; @@ -143,11 +144,13 @@ export async function decompileGameData(filePath: string): Promise { installLog.info(output.stdout, { game: SupportedGame.Jak1, }); + ProcessLogs.update((currLogs) => currLogs + output.stdout); } if (output.stderr) { installLog.error(output.stderr, { game: SupportedGame.Jak1, }); + ProcessLogs.update((currLogs) => currLogs + output.stderr); } if (output.code === 0) { return true; @@ -175,11 +178,13 @@ export async function compileGame(filePath: string): Promise { installLog.info(output.stdout, { game: SupportedGame.Jak1, }); + ProcessLogs.update((currLogs) => currLogs + output.stdout); } if (output.stderr) { installLog.error(output.stderr, { game: SupportedGame.Jak1, }); + ProcessLogs.update((currLogs) => currLogs + output.stderr); } if (output.code === 0) { InstallStatus.update(() => SETUP_SUCCESS.ready); @@ -193,12 +198,13 @@ export async function fullInstallation(game: SupportedGame): Promise { isInstalling.update(() => true); try { isoPath = await isoPrompt(); + ProcessLogs.update(() => ""); await extractAndValidateISO(isoPath); await decompileGameData(isoPath); await compileGame(isoPath); - await setInstallStatus(game, true); + await launcherConfig.setInstallStatus(game, true); isInstalling.update(() => false); - await setGameInstallVersion(game); + await launcherConfig.setGameInstallVersion(game); return true; } catch (err) { installLog.error("unexpected error encountered", { @@ -226,7 +232,7 @@ export async function recompileGame(game: SupportedGame) { await decompileGameData(isoPath); await compileGame(isoPath); // update settings.json with latest tools version from metadata.json - await setGameInstallVersion(game); + await launcherConfig.setGameInstallVersion(game); isInstalling.update(() => false); } catch (err) { installLog.error("unexpected error encountered", { diff --git a/src/lib/stores/AppStore.ts b/src/lib/stores/AppStore.ts index f61a966..8532d9d 100644 --- a/src/lib/stores/AppStore.ts +++ b/src/lib/stores/AppStore.ts @@ -6,4 +6,4 @@ export const InstallStatus = writable({ }); export const isInstalling = writable(false); export const gameNeedsReinstall = writable(false); -export const ProcessLogs = writable(); +export const ProcessLogs = writable(""); diff --git a/src/routes/Game.svelte b/src/routes/Game.svelte index b2efa98..be9778f 100644 --- a/src/routes/Game.svelte +++ b/src/routes/Game.svelte @@ -1,6 +1,6 @@ {#if componentLoaded}
-

+

{getGameTitle(activeGame)}

{#if isGameInstalled && !$gameNeedsReinstall} @@ -102,3 +102,9 @@ {:else} {/if} + + diff --git a/src/splash/Splash.svelte b/src/splash/Splash.svelte index d8512b5..1b4a7a3 100644 --- a/src/splash/Splash.svelte +++ b/src/splash/Splash.svelte @@ -1,7 +1,5 @@