mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-14 20:22:10 +00:00
feat: refactoring achievements watcher
This commit is contained in:
parent
c18c41ac95
commit
44e59a5f6f
12 changed files with 120 additions and 57 deletions
|
@ -1,11 +0,0 @@
|
||||||
import { registerEvent } from "../register-event";
|
|
||||||
import { updateLocalUnlockedAchivements } from "@main/services/achievements/update-local-unlocked-achivements";
|
|
||||||
|
|
||||||
const updateGameUnlockedAchievements = async (
|
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
|
||||||
objectId: string
|
|
||||||
) => {
|
|
||||||
return updateLocalUnlockedAchivements(false, objectId);
|
|
||||||
};
|
|
||||||
|
|
||||||
registerEvent("updateGameUnlockedAchievements", updateGameUnlockedAchievements);
|
|
|
@ -10,7 +10,6 @@ import "./catalogue/search-games";
|
||||||
import "./catalogue/get-game-stats";
|
import "./catalogue/get-game-stats";
|
||||||
import "./catalogue/get-trending-games";
|
import "./catalogue/get-trending-games";
|
||||||
import "./catalogue/get-game-achievements";
|
import "./catalogue/get-game-achievements";
|
||||||
import "./catalogue/update-game-unlocked-achievements";
|
|
||||||
import "./hardware/get-disk-free-space";
|
import "./hardware/get-disk-free-space";
|
||||||
import "./library/add-game-to-library";
|
import "./library/add-game-to-library";
|
||||||
import "./library/create-game-shortcut";
|
import "./library/create-game-shortcut";
|
||||||
|
|
16
src/main/services/achievements/achievement-watcher.ts
Normal file
16
src/main/services/achievements/achievement-watcher.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { gameRepository } from "@main/repository";
|
||||||
|
import { startGameAchievementObserver } from "./game-achievements-observer";
|
||||||
|
|
||||||
|
export const watchAchievements = async () => {
|
||||||
|
const games = await gameRepository.find({
|
||||||
|
where: {
|
||||||
|
isDeleted: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (games.length === 0) return;
|
||||||
|
|
||||||
|
for (const game of games) {
|
||||||
|
startGameAchievementObserver(game);
|
||||||
|
}
|
||||||
|
};
|
|
@ -35,7 +35,40 @@ const getObjectIdsInFolder = (path: string) => {
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const findSteamGameAchievementFiles = (objectId?: string) => {
|
export const findSteamGameAchievementFiles = (objectId: string) => {
|
||||||
|
const crackers = [
|
||||||
|
Cracker.codex,
|
||||||
|
Cracker.goldberg,
|
||||||
|
Cracker.rune,
|
||||||
|
Cracker.onlineFix,
|
||||||
|
];
|
||||||
|
|
||||||
|
const achievementFiles: AchievementFile[] = [];
|
||||||
|
for (const cracker of crackers) {
|
||||||
|
let achievementPath: string;
|
||||||
|
let fileLocation: string[];
|
||||||
|
|
||||||
|
if (cracker === Cracker.onlineFix) {
|
||||||
|
achievementPath = path.join(publicDir, Cracker.onlineFix);
|
||||||
|
fileLocation = ["Stats", "Achievements.ini"];
|
||||||
|
} else if (cracker === Cracker.goldberg) {
|
||||||
|
achievementPath = path.join(appData, "Goldberg SteamEmu Saves");
|
||||||
|
fileLocation = ["achievements.json"];
|
||||||
|
} else {
|
||||||
|
achievementPath = path.join(publicDir, "Steam", cracker);
|
||||||
|
fileLocation = ["achievements.ini"];
|
||||||
|
}
|
||||||
|
|
||||||
|
achievementFiles.push({
|
||||||
|
type: cracker,
|
||||||
|
filePath: path.join(achievementPath, objectId, ...fileLocation),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return achievementFiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findAllSteamGameAchievementFiles = () => {
|
||||||
const gameAchievementFiles = new Map<string, AchievementFile[]>();
|
const gameAchievementFiles = new Map<string, AchievementFile[]>();
|
||||||
|
|
||||||
const crackers = [
|
const crackers = [
|
||||||
|
@ -60,9 +93,7 @@ export const findSteamGameAchievementFiles = (objectId?: string) => {
|
||||||
fileLocation = ["achievements.ini"];
|
fileLocation = ["achievements.ini"];
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectIds = objectId
|
const objectIds = getObjectIdsInFolder(achievementPath);
|
||||||
? [objectId]
|
|
||||||
: getObjectIdsInFolder(achievementPath);
|
|
||||||
|
|
||||||
for (const objectId of objectIds) {
|
for (const objectId of objectIds) {
|
||||||
addGame(
|
addGame(
|
||||||
|
|
|
@ -37,14 +37,10 @@ const processAchievementFile = async (game: Game, file: AchievementFile) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const startFileWatch = async (game: Game, file: AchievementFile) => {
|
const startFileWatch = async (game: Game, file: AchievementFile) => {
|
||||||
const signal = gameAchievementObserver[game.id]?.signal;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await processAchievementFile(game, file);
|
await processAchievementFile(game, file);
|
||||||
|
|
||||||
const watcher = watch(file.filePath, {
|
const watcher = watch(file.filePath);
|
||||||
signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
for await (const event of watcher) {
|
for await (const event of watcher) {
|
||||||
if (event.eventType === "change") {
|
if (event.eventType === "change") {
|
||||||
|
@ -62,8 +58,7 @@ export const startGameAchievementObserver = async (game: Game) => {
|
||||||
if (game.shop !== "steam") return;
|
if (game.shop !== "steam") return;
|
||||||
if (gameAchievementObserver[game.id]) return;
|
if (gameAchievementObserver[game.id]) return;
|
||||||
|
|
||||||
const achievementFiles =
|
const achievementFiles = findSteamGameAchievementFiles(game.objectID);
|
||||||
findSteamGameAchievementFiles(game.objectID).get(game.objectID) || [];
|
|
||||||
|
|
||||||
logger.log(
|
logger.log(
|
||||||
"Achievements files to observe for:",
|
"Achievements files to observe for:",
|
||||||
|
@ -84,8 +79,3 @@ export const startGameAchievementObserver = async (game: Game) => {
|
||||||
startFileWatch(game, file);
|
startFileWatch(game, file);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stopGameAchievementObserver = (gameId: number) => {
|
|
||||||
gameAchievementObserver[gameId]?.abort();
|
|
||||||
delete gameAchievementObserver[gameId];
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { gameAchievementRepository, gameRepository } from "@main/repository";
|
import { gameAchievementRepository, gameRepository } from "@main/repository";
|
||||||
import { findSteamGameAchievementFiles } from "./find-steam-game-achivement-files";
|
import {
|
||||||
|
findAllSteamGameAchievementFiles,
|
||||||
|
findSteamGameAchievementFiles,
|
||||||
|
} from "./find-steam-game-achivement-files";
|
||||||
import { parseAchievementFile } from "./parse-achievement-file";
|
import { parseAchievementFile } from "./parse-achievement-file";
|
||||||
import { checkUnlockedAchievements } from "./check-unlocked-achievements";
|
import { checkUnlockedAchievements } from "./check-unlocked-achievements";
|
||||||
import { mergeAchievements } from "./merge-achievements";
|
import { mergeAchievements } from "./merge-achievements";
|
||||||
import type { UnlockedAchievement } from "@types";
|
import type { UnlockedAchievement } from "@types";
|
||||||
import { getGameAchievementData } from "./get-game-achievement-data";
|
import { getGameAchievementData } from "./get-game-achievement-data";
|
||||||
|
|
||||||
export const updateLocalUnlockedAchivements = async (
|
export const updateAllLocalUnlockedAchievements = async () => {
|
||||||
publishNotification: boolean,
|
const gameAchievementFiles = findAllSteamGameAchievementFiles();
|
||||||
objectId?: string
|
|
||||||
) => {
|
|
||||||
const gameAchievementFiles = findSteamGameAchievementFiles(objectId);
|
|
||||||
|
|
||||||
for (const objectId of gameAchievementFiles.keys()) {
|
for (const objectId of gameAchievementFiles.keys()) {
|
||||||
const [game, localAchievements] = await Promise.all([
|
const [game, localAchievements] = await Promise.all([
|
||||||
|
@ -62,11 +62,62 @@ export const updateLocalUnlockedAchivements = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeAchievements(
|
mergeAchievements(objectId, "steam", unlockedAchievements, false);
|
||||||
objectId,
|
|
||||||
"steam",
|
|
||||||
unlockedAchievements,
|
|
||||||
publishNotification
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateLocalUnlockedAchivements = async (
|
||||||
|
publishNotification: boolean,
|
||||||
|
objectId: string
|
||||||
|
) => {
|
||||||
|
const gameAchievementFiles = findSteamGameAchievementFiles(objectId);
|
||||||
|
|
||||||
|
const [game, localAchievements] = await Promise.all([
|
||||||
|
gameRepository.findOne({
|
||||||
|
where: { objectID: objectId, shop: "steam", isDeleted: false },
|
||||||
|
}),
|
||||||
|
gameAchievementRepository.findOne({
|
||||||
|
where: { objectId, shop: "steam" },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!game) return;
|
||||||
|
|
||||||
|
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(
|
||||||
|
achievementFile.filePath
|
||||||
|
);
|
||||||
|
|
||||||
|
if (localAchievementFile) {
|
||||||
|
unlockedAchievements.push(
|
||||||
|
...checkUnlockedAchievements(achievementFile.type, localAchievementFile)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeAchievements(
|
||||||
|
objectId,
|
||||||
|
"steam",
|
||||||
|
unlockedAchievements,
|
||||||
|
publishNotification
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { IsNull } from "typeorm";
|
||||||
import { HydraApi } from "../hydra-api";
|
import { HydraApi } from "../hydra-api";
|
||||||
import { mergeWithRemoteGames } from "./merge-with-remote-games";
|
import { mergeWithRemoteGames } from "./merge-with-remote-games";
|
||||||
import { WindowManager } from "../window-manager";
|
import { WindowManager } from "../window-manager";
|
||||||
import { updateLocalUnlockedAchivements } from "../achievements/update-local-unlocked-achivements";
|
import { updateAllLocalUnlockedAchievements } from "../achievements/update-local-unlocked-achivements";
|
||||||
|
|
||||||
export const uploadGamesBatch = async () => {
|
export const uploadGamesBatch = async () => {
|
||||||
const games = await gameRepository.find({
|
const games = await gameRepository.find({
|
||||||
|
@ -29,7 +29,7 @@ export const uploadGamesBatch = async () => {
|
||||||
|
|
||||||
await mergeWithRemoteGames();
|
await mergeWithRemoteGames();
|
||||||
|
|
||||||
await updateLocalUnlockedAchivements(false);
|
await updateAllLocalUnlockedAchievements();
|
||||||
|
|
||||||
if (WindowManager.mainWindow)
|
if (WindowManager.mainWindow)
|
||||||
WindowManager.mainWindow.webContents.send("on-library-batch-complete");
|
WindowManager.mainWindow.webContents.send("on-library-batch-complete");
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { sleep } from "@main/helpers";
|
import { sleep } from "@main/helpers";
|
||||||
import { DownloadManager } from "./download";
|
import { DownloadManager } from "./download";
|
||||||
import { watchProcesses } from "./process-watcher";
|
import { watchProcesses } from "./process-watcher";
|
||||||
|
import { watchAchievements } from "./achievements/achievement-watcher";
|
||||||
|
|
||||||
export const startMainLoop = async () => {
|
export const startMainLoop = async () => {
|
||||||
// eslint-disable-next-line no-constant-condition
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
@ -8,6 +9,7 @@ export const startMainLoop = async () => {
|
||||||
await Promise.allSettled([
|
await Promise.allSettled([
|
||||||
watchProcesses(),
|
watchProcesses(),
|
||||||
DownloadManager.watchDownloads(),
|
DownloadManager.watchDownloads(),
|
||||||
|
watchAchievements(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
|
|
|
@ -5,10 +5,6 @@ import { createGame, updateGamePlaytime } from "./library-sync";
|
||||||
import { GameRunning } from "@types";
|
import { GameRunning } from "@types";
|
||||||
import { PythonInstance } from "./download";
|
import { PythonInstance } from "./download";
|
||||||
import { Game } from "@main/entity";
|
import { Game } from "@main/entity";
|
||||||
import {
|
|
||||||
startGameAchievementObserver,
|
|
||||||
stopGameAchievementObserver,
|
|
||||||
} from "@main/services/achievements/game-achievements-observer";
|
|
||||||
|
|
||||||
export const gamesPlaytime = new Map<
|
export const gamesPlaytime = new Map<
|
||||||
number,
|
number,
|
||||||
|
@ -78,8 +74,6 @@ function onOpenGame(game: Game) {
|
||||||
} else {
|
} else {
|
||||||
createGame({ ...game, lastTimePlayed: new Date() }).catch(() => {});
|
createGame({ ...game, lastTimePlayed: new Date() }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
startGameAchievementObserver(game);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTickGame(game: Game) {
|
function onTickGame(game: Game) {
|
||||||
|
@ -116,8 +110,6 @@ function onTickGame(game: Game) {
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
startGameAchievementObserver(game);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCloseGame = (game: Game) => {
|
const onCloseGame = (game: Game) => {
|
||||||
|
@ -133,6 +125,4 @@ const onCloseGame = (game: Game) => {
|
||||||
} else {
|
} else {
|
||||||
createGame(game).catch(() => {});
|
createGame(game).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
stopGameAchievementObserver(game.id);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,8 +51,6 @@ contextBridge.exposeInMainWorld("electron", {
|
||||||
getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"),
|
getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"),
|
||||||
getGameAchievements: (objectId: string, shop: GameShop) =>
|
getGameAchievements: (objectId: string, shop: GameShop) =>
|
||||||
ipcRenderer.invoke("getGameAchievements", objectId, shop),
|
ipcRenderer.invoke("getGameAchievements", objectId, shop),
|
||||||
updateGameUnlockedAchievements: (objectId: string) =>
|
|
||||||
ipcRenderer.invoke("updateGameUnlockedAchievements", objectId),
|
|
||||||
onAchievementUnlocked: (
|
onAchievementUnlocked: (
|
||||||
cb: (
|
cb: (
|
||||||
objectId: string,
|
objectId: string,
|
||||||
|
|
|
@ -179,8 +179,6 @@ export function GameDetailsContextProvider({
|
||||||
}, [game?.id, isGameRunning, updateGame]);
|
}, [game?.id, isGameRunning, updateGame]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.electron.updateGameUnlockedAchievements(objectID!).catch(() => {});
|
|
||||||
|
|
||||||
const unsubscribe = window.electron.onAchievementUnlocked(
|
const unsubscribe = window.electron.onAchievementUnlocked(
|
||||||
(objectId, shop) => {
|
(objectId, shop) => {
|
||||||
if (objectID !== objectId || shop !== shop) return;
|
if (objectID !== objectId || shop !== shop) return;
|
||||||
|
|
1
src/renderer/src/declaration.d.ts
vendored
1
src/renderer/src/declaration.d.ts
vendored
|
@ -70,7 +70,6 @@ declare global {
|
||||||
objectId: string,
|
objectId: string,
|
||||||
shop: GameShop
|
shop: GameShop
|
||||||
) => Promise<GameAchievement[]>;
|
) => Promise<GameAchievement[]>;
|
||||||
updateGameUnlockedAchievements: (objectId: string) => Promise<void>;
|
|
||||||
onAchievementUnlocked: (
|
onAchievementUnlocked: (
|
||||||
cb: (
|
cb: (
|
||||||
objectId: string,
|
objectId: string,
|
||||||
|
|
Loading…
Reference in a new issue