diff --git a/src/main/events/catalogue/get-game-achievements.ts b/src/main/events/catalogue/get-game-achievements.ts index f0fa2c94..456babf4 100644 --- a/src/main/events/catalogue/get-game-achievements.ts +++ b/src/main/events/catalogue/get-game-achievements.ts @@ -1,88 +1,45 @@ import type { GameAchievement, GameShop } from "@types"; import { registerEvent } from "../register-event"; -import { HydraApi } from "@main/services"; -import { - gameAchievementRepository, - gameRepository, - userPreferencesRepository, -} from "@main/repository"; -import { UserNotLoggedInError } from "@shared"; -import { Game } from "@main/entity"; - -const getAchievementsDataFromApi = async ( - objectId: string, - shop: string, - game: Game | null -) => { - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, - }); - - return HydraApi.get("/games/achievements", { - objectId, - shop, - language: userPreferences?.language || "en", - }) - .then((achievements) => { - if (game) { - gameAchievementRepository.upsert( - { - objectId, - shop, - achievements: JSON.stringify(achievements), - }, - ["objectId", "shop"] - ); - } - - return achievements; - }) - .catch((err) => { - if (err instanceof UserNotLoggedInError) throw err; - return []; - }); -}; +import { gameAchievementRepository } from "@main/repository"; +import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data"; const getGameAchievements = async ( _event: Electron.IpcMainInvokeEvent, objectId: string, shop: GameShop ): Promise => { - const [game, cachedAchievements] = await Promise.all([ - gameRepository.findOne({ - where: { objectID: objectId, shop }, - }), - gameAchievementRepository.findOne({ where: { objectId, shop } }), - ]); + const cachedAchievements = await gameAchievementRepository.findOne({ + where: { objectId, shop }, + }); - const gameAchievements = cachedAchievements?.achievements + const achievementsData = cachedAchievements?.achievements ? JSON.parse(cachedAchievements.achievements) - : await getAchievementsDataFromApi(objectId, shop, game); + : await getGameAchievementData(objectId, shop); const unlockedAchievements = JSON.parse( cachedAchievements?.unlockedAchievements || "[]" ) as { name: string; unlockTime: number }[]; - return gameAchievements - .map((achievement) => { + return achievementsData + .map((achievementData) => { const unlockedAchiement = unlockedAchievements.find( (localAchievement) => { return ( localAchievement.name.toUpperCase() == - achievement.name.toUpperCase() + achievementData.name.toUpperCase() ); } ); if (unlockedAchiement) { return { - ...achievement, + ...achievementData, unlocked: true, unlockTime: unlockedAchiement.unlockTime, }; } - return { ...achievement, unlocked: false, unlockTime: null }; + return { ...achievementData, unlocked: false, unlockTime: null }; }) .sort((a, b) => { if (a.unlocked && !b.unlocked) return -1; diff --git a/src/main/events/library/add-game-to-library.ts b/src/main/events/library/add-game-to-library.ts index aa8aaab8..898c25cd 100644 --- a/src/main/events/library/add-game-to-library.ts +++ b/src/main/events/library/add-game-to-library.ts @@ -44,12 +44,12 @@ const addGameToLibrary = async ( }); } - updateLocalUnlockedAchivements(objectId); - const game = await gameRepository.findOne({ where: { objectID: objectId }, }); + updateLocalUnlockedAchivements(game!); + createGame(game!).catch(() => {}); }); }; diff --git a/src/main/services/achievements/achievement-watcher.ts b/src/main/services/achievements/achievement-watcher.ts index eecb71d5..166e6cff 100644 --- a/src/main/services/achievements/achievement-watcher.ts +++ b/src/main/services/achievements/achievement-watcher.ts @@ -6,6 +6,7 @@ import fs, { readdirSync } from "node:fs"; import { findAchievementFileInExecutableDirectory, findAllAchievementFiles, + getAlternativeObjectIds, } from "./find-achivement-files"; import type { AchievementFile } from "@types"; import { achievementsLogger, logger } from "../logger"; @@ -23,25 +24,27 @@ export const watchAchievements = async () => { if (games.length === 0) return; - const achievementFiles = await findAllAchievementFiles(); + const achievementFiles = findAllAchievementFiles(); for (const game of games) { - const gameAchievementFiles = achievementFiles.get(game.objectID) || []; - const achievementFileInsideDirectory = - findAchievementFileInExecutableDirectory(game); + for (const objectId of getAlternativeObjectIds(game.objectID)) { + const gameAchievementFiles = achievementFiles.get(objectId) || []; + const achievementFileInsideDirectory = + findAchievementFileInExecutableDirectory(game); - gameAchievementFiles.push(...achievementFileInsideDirectory); + gameAchievementFiles.push(...achievementFileInsideDirectory); - if (!gameAchievementFiles.length) continue; + if (!gameAchievementFiles.length) continue; - console.log( - "Achievements files to observe for:", - game.title, - gameAchievementFiles - ); + console.log( + "Achievements files to observe for:", + game.title, + gameAchievementFiles + ); - for (const file of gameAchievementFiles) { - compareFile(game, file); + for (const file of gameAchievementFiles) { + compareFile(game, file); + } } } }; @@ -50,10 +53,7 @@ const processAchievementFileDiff = async ( game: Game, file: AchievementFile ) => { - const unlockedAchievements = await parseAchievementFile( - file.filePath, - file.type - ); + const unlockedAchievements = parseAchievementFile(file.filePath, file.type); logger.log("Achievements from file", file.filePath, unlockedAchievements); diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index c7aa4950..ebfbefca 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -27,7 +27,7 @@ const crackers = [ Cracker.flt, ]; -const getPathFromCracker = async (cracker: Cracker) => { +const getPathFromCracker = (cracker: Cracker) => { if (cracker === Cracker.codex) { return [ { @@ -167,7 +167,8 @@ const getPathFromCracker = async (cracker: Cracker) => { throw new Error(`Cracker ${cracker} not implemented`); }; -const getAlternativeObjectIds = (objectId: string) => { +export const getAlternativeObjectIds = (objectId: string) => { + // Dishonored if (objectId === "205100") { return ["205100", "217980", "31292"]; } @@ -175,13 +176,11 @@ const getAlternativeObjectIds = (objectId: string) => { return [objectId]; }; -export const findAchievementFiles = async (game: Game) => { +export const findAchievementFiles = (game: Game) => { const achievementFiles: AchievementFile[] = []; for (const cracker of crackers) { - for (const { folderPath, fileLocation } of await getPathFromCracker( - cracker - )) { + for (const { folderPath, fileLocation } of getPathFromCracker(cracker)) { for (const objectId of getAlternativeObjectIds(game.objectID)) { const filePath = path.join(folderPath, objectId, ...fileLocation); @@ -229,13 +228,11 @@ export const findAchievementFileInExecutableDirectory = ( ]; }; -export const findAllAchievementFiles = async () => { +export const findAllAchievementFiles = () => { const gameAchievementFiles = new Map(); for (const cracker of crackers) { - for (const { folderPath, fileLocation } of await getPathFromCracker( - cracker - )) { + for (const { folderPath, fileLocation } of getPathFromCracker(cracker)) { if (!fs.existsSync(folderPath)) { continue; } diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index 79e551ac..7f1e6b5a 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -1,4 +1,7 @@ -import { userPreferencesRepository } from "@main/repository"; +import { + gameAchievementRepository, + userPreferencesRepository, +} from "@main/repository"; import { HydraApi } from "../hydra-api"; export const getGameAchievementData = async ( @@ -13,5 +16,18 @@ export const getGameAchievementData = async ( shop, objectId, language: userPreferences?.language || "en", - }); + }) + .then(async (achievements) => { + await gameAchievementRepository.upsert( + { + objectId, + shop, + achievements: JSON.stringify(achievements), + }, + ["objectId", "shop"] + ); + + return achievements; + }) + .catch(() => []); }; diff --git a/src/main/services/achievements/parse-achievement-file.ts b/src/main/services/achievements/parse-achievement-file.ts index 5817b032..47c8e805 100644 --- a/src/main/services/achievements/parse-achievement-file.ts +++ b/src/main/services/achievements/parse-achievement-file.ts @@ -1,57 +1,51 @@ import { Cracker } from "@shared"; import { UnlockedAchievement } from "@types"; -import { - existsSync, - createReadStream, - readFileSync, - readdirSync, -} from "node:fs"; -import readline from "node:readline"; +import { existsSync, readFileSync, readdirSync } from "node:fs"; import { achievementsLogger } from "../logger"; -export const parseAchievementFile = async ( +export const parseAchievementFile = ( filePath: string, type: Cracker -): Promise => { +): UnlockedAchievement[] => { if (!existsSync(filePath)) return []; if (type == Cracker.codex) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processDefault(parsed); } if (type == Cracker.rune) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processDefault(parsed); } if (type === Cracker.onlineFix) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processOnlineFix(parsed); } if (type === Cracker.goldberg) { - const parsed = await jsonParse(filePath); + const parsed = jsonParse(filePath); return processGoldberg(parsed); } if (type == Cracker.userstats) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processUserStats(parsed); } if (type == Cracker.rld) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processRld(parsed); } if (type === Cracker.skidrow) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processSkidrow(parsed); } if (type === Cracker._3dm) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return process3DM(parsed); } @@ -67,27 +61,24 @@ export const parseAchievementFile = async ( } if (type === Cracker.creamAPI) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processCreamAPI(parsed); } - achievementsLogger.log(`${type} achievements found on ${filePath}`); + achievementsLogger.log( + `Unprocessed ${type} achievements found on ${filePath}` + ); return []; }; -const iniParse = async (filePath: string) => { +const iniParse = (filePath: string) => { try { - const file = createReadStream(filePath); - - const lines = readline.createInterface({ - input: file, - crlfDelay: Infinity, - }); + const lines = readFileSync(filePath, "utf-8").split(/[\r\n]+/); let objectName = ""; const object: Record> = {}; - for await (const line of lines) { + for (const line of lines) { if (line.startsWith("###") || !line.length) continue; if (line.startsWith("[") && line.endsWith("]")) { @@ -100,7 +91,8 @@ const iniParse = async (filePath: string) => { } return object; - } catch { + } catch (err) { + achievementsLogger.error(`Error parsing ${filePath}`, err); return null; } }; @@ -108,7 +100,8 @@ const iniParse = async (filePath: string) => { const jsonParse = (filePath: string) => { try { return JSON.parse(readFileSync(filePath, "utf-8")); - } catch { + } catch (err) { + achievementsLogger.error(`Error parsing ${filePath}`, err); return null; } }; diff --git a/src/main/services/achievements/update-local-unlocked-achivements.ts b/src/main/services/achievements/update-local-unlocked-achivements.ts index 963ce602..13f33fcd 100644 --- a/src/main/services/achievements/update-local-unlocked-achivements.ts +++ b/src/main/services/achievements/update-local-unlocked-achivements.ts @@ -3,15 +3,17 @@ import { findAllAchievementFiles, findAchievementFiles, findAchievementFileInExecutableDirectory, + getAlternativeObjectIds, } from "./find-achivement-files"; import { parseAchievementFile } from "./parse-achievement-file"; import { mergeAchievements } from "./merge-achievements"; import type { UnlockedAchievement } from "@types"; import { getGameAchievementData } from "./get-game-achievement-data"; import { achievementsLogger } from "../logger"; +import { Game } from "@main/entity"; export const updateAllLocalUnlockedAchievements = async () => { - const gameAchievementFilesMap = await findAllAchievementFiles(); + const gameAchievementFilesMap = findAllAchievementFiles(); const games = await gameRepository.find({ where: { @@ -20,69 +22,50 @@ export const updateAllLocalUnlockedAchievements = async () => { }); for (const game of games) { - const gameAchievementFiles = - gameAchievementFilesMap.get(game.objectID) || []; - const achievementFileInsideDirectory = - findAchievementFileInExecutableDirectory(game); + for (const objectId of getAlternativeObjectIds(game.objectID)) { + const gameAchievementFiles = gameAchievementFilesMap.get(objectId) || []; + const achievementFileInsideDirectory = + findAchievementFileInExecutableDirectory(game); - gameAchievementFiles.push(...achievementFileInsideDirectory); + gameAchievementFiles.push(...achievementFileInsideDirectory); - const localAchievements = await gameAchievementRepository.findOne({ - where: { objectId: game.objectID, shop: "steam" }, - }); - - if (!localAchievements || !localAchievements.achievements) { - await getGameAchievementData(game.objectID, "steam") - .then((achievements) => { - return gameAchievementRepository.upsert( - { - objectId: game.objectID, - shop: "steam", - achievements: JSON.stringify(achievements), - }, - ["objectId", "shop"] - ); + gameAchievementRepository + .findOne({ + where: { objectId: game.objectID, shop: "steam" }, }) - .catch(() => {}); - } + .then((localAchievements) => { + if (!localAchievements || !localAchievements.achievements) { + getGameAchievementData(game.objectID, "steam"); + } + }); - const unlockedAchievements: UnlockedAchievement[] = []; + const unlockedAchievements: UnlockedAchievement[] = []; - for (const achievementFile of gameAchievementFiles) { - const parsedAchievements = await parseAchievementFile( - achievementFile.filePath, - achievementFile.type - ); + for (const achievementFile of gameAchievementFiles) { + const parsedAchievements = parseAchievementFile( + achievementFile.filePath, + achievementFile.type + ); - if (parsedAchievements.length) { - unlockedAchievements.push(...parsedAchievements); + if (parsedAchievements.length) { + unlockedAchievements.push(...parsedAchievements); + } + + achievementsLogger.log( + "Achievement file for", + game.title, + achievementFile.filePath, + parsedAchievements + ); } - achievementsLogger.log( - "Achievement file for", - game.title, - achievementFile.filePath, - parsedAchievements - ); + mergeAchievements(game.objectID, "steam", unlockedAchievements, false); } - - mergeAchievements(game.objectID, "steam", unlockedAchievements, false); } }; -export const updateLocalUnlockedAchivements = async (objectId: string) => { - const [game, localAchievements] = await Promise.all([ - gameRepository.findOne({ - where: { objectID: objectId, shop: "steam", isDeleted: false }, - }), - gameAchievementRepository.findOne({ - where: { objectId, shop: "steam" }, - }), - ]); - - if (!game) return; - - const gameAchievementFiles = await findAchievementFiles(game); +export const updateLocalUnlockedAchivements = async (game: Game) => { + const gameAchievementFiles = findAchievementFiles(game); const achievementFileInsideDirectory = findAchievementFileInExecutableDirectory(game); @@ -91,25 +74,10 @@ export const updateLocalUnlockedAchivements = async (objectId: string) => { console.log("Achievements files for", game.title, gameAchievementFiles); - if (!localAchievements || !localAchievements.achievements) { - await getGameAchievementData(objectId, "steam") - .then((achievements) => { - return gameAchievementRepository.upsert( - { - objectId, - shop: "steam", - achievements: JSON.stringify(achievements), - }, - ["objectId", "shop"] - ); - }) - .catch(() => {}); - } - const unlockedAchievements: UnlockedAchievement[] = []; for (const achievementFile of gameAchievementFiles) { - const localAchievementFile = await parseAchievementFile( + const localAchievementFile = parseAchievementFile( achievementFile.filePath, achievementFile.type ); @@ -119,5 +87,5 @@ export const updateLocalUnlockedAchivements = async (objectId: string) => { } } - mergeAchievements(objectId, "steam", unlockedAchievements, false); + mergeAchievements(game.objectID, "steam", unlockedAchievements, false); }; diff --git a/src/main/services/main-loop.ts b/src/main/services/main-loop.ts index f45956f2..5ba57fc3 100644 --- a/src/main/services/main-loop.ts +++ b/src/main/services/main-loop.ts @@ -12,6 +12,6 @@ export const startMainLoop = async () => { watchAchievements(), ]); - await sleep(1000); + await sleep(1500); } };