mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-13 03:32:13 +00:00
feat: optimizations
This commit is contained in:
parent
fd5262cd6e
commit
27e8a0820f
6 changed files with 167 additions and 116 deletions
|
@ -40,10 +40,8 @@ const watchAchievementsWindows = async () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gameAchievementFiles.length) continue;
|
|
||||||
|
|
||||||
for (const file of gameAchievementFiles) {
|
for (const file of gameAchievementFiles) {
|
||||||
await compareFile(game, file);
|
compareFile(game, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -56,8 +54,6 @@ const watchAchievementsWithWine = async () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (games.length === 0) return;
|
|
||||||
|
|
||||||
for (const game of games) {
|
for (const game of games) {
|
||||||
const gameAchievementFiles = findAchievementFiles(game);
|
const gameAchievementFiles = findAchievementFiles(game);
|
||||||
const achievementFileInsideDirectory =
|
const achievementFileInsideDirectory =
|
||||||
|
@ -65,10 +61,8 @@ const watchAchievementsWithWine = async () => {
|
||||||
|
|
||||||
gameAchievementFiles.push(...achievementFileInsideDirectory);
|
gameAchievementFiles.push(...achievementFileInsideDirectory);
|
||||||
|
|
||||||
if (!gameAchievementFiles.length) continue;
|
|
||||||
|
|
||||||
for (const file of gameAchievementFiles) {
|
for (const file of gameAchievementFiles) {
|
||||||
await compareFile(game, file);
|
compareFile(game, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -104,7 +98,7 @@ const compareFile = (game: Game, file: AchievementFile) => {
|
||||||
const previousStat = fileStats.get(file.filePath);
|
const previousStat = fileStats.get(file.filePath);
|
||||||
fileStats.set(file.filePath, currentStat.mtimeMs);
|
fileStats.set(file.filePath, currentStat.mtimeMs);
|
||||||
|
|
||||||
if (!previousStat) {
|
if (!previousStat || previousStat === -1) {
|
||||||
if (currentStat.mtimeMs) {
|
if (currentStat.mtimeMs) {
|
||||||
achievementsLogger.log(
|
achievementsLogger.log(
|
||||||
"First change in file",
|
"First change in file",
|
||||||
|
@ -153,17 +147,55 @@ const processAchievementFileDiff = async (
|
||||||
export class AchievementWatcherManager {
|
export class AchievementWatcherManager {
|
||||||
private static hasFinishedMergingWithRemote = false;
|
private static hasFinishedMergingWithRemote = false;
|
||||||
|
|
||||||
public static watchAchievements = async () => {
|
public static watchAchievements = () => {
|
||||||
if (!this.hasFinishedMergingWithRemote) return;
|
if (!this.hasFinishedMergingWithRemote) return;
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
return watchAchievementsWindows();
|
return watchAchievementsWindows();
|
||||||
}
|
}
|
||||||
|
|
||||||
watchAchievementsWithWine();
|
return watchAchievementsWithWine();
|
||||||
};
|
};
|
||||||
|
|
||||||
public static preSearchAchievements = async () => {
|
private static preProcessGameAchievementFiles = (
|
||||||
|
game: Game,
|
||||||
|
gameAchievementFiles: AchievementFile[]
|
||||||
|
) => {
|
||||||
|
const unlockedAchievements: UnlockedAchievement[] = [];
|
||||||
|
for (const achievementFile of gameAchievementFiles) {
|
||||||
|
const parsedAchievements = parseAchievementFile(
|
||||||
|
achievementFile.filePath,
|
||||||
|
achievementFile.type
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const currentStat = fs.statSync(achievementFile.filePath);
|
||||||
|
fileStats.set(achievementFile.filePath, currentStat.mtimeMs);
|
||||||
|
} catch {
|
||||||
|
fileStats.set(achievementFile.filePath, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedAchievements.length) {
|
||||||
|
unlockedAchievements.push(...parsedAchievements);
|
||||||
|
|
||||||
|
achievementsLogger.log(
|
||||||
|
"Achievement file for",
|
||||||
|
game.title,
|
||||||
|
achievementFile.filePath,
|
||||||
|
parsedAchievements
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeAchievements(
|
||||||
|
game.objectID,
|
||||||
|
"steam",
|
||||||
|
unlockedAchievements,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private static preSearchAchievementsWindows = async () => {
|
||||||
const games = await gameRepository.find({
|
const games = await gameRepository.find({
|
||||||
where: {
|
where: {
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
|
@ -172,20 +204,19 @@ export class AchievementWatcherManager {
|
||||||
|
|
||||||
const gameAchievementFilesMap = findAllAchievementFiles();
|
const gameAchievementFilesMap = findAllAchievementFiles();
|
||||||
|
|
||||||
await Promise.all(
|
return Promise.all(
|
||||||
games.map(async (game) => {
|
games.map((game) => {
|
||||||
gameAchievementRepository
|
gameAchievementRepository
|
||||||
.findOne({
|
.findOne({
|
||||||
where: { objectId: game.objectID, shop: "steam" },
|
where: { objectId: game.objectID, shop: game.shop },
|
||||||
})
|
})
|
||||||
.then((localAchievements) => {
|
.then((localAchievements) => {
|
||||||
if (!localAchievements || !localAchievements.achievements) {
|
if (!localAchievements || !localAchievements.achievements) {
|
||||||
getGameAchievementData(game.objectID, "steam");
|
getGameAchievementData(game.objectID, game.shop);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const gameAchievementFiles: AchievementFile[] = [];
|
const gameAchievementFiles: AchievementFile[] = [];
|
||||||
const unlockedAchievements: UnlockedAchievement[] = [];
|
|
||||||
|
|
||||||
for (const objectId of getAlternativeObjectIds(game.objectID)) {
|
for (const objectId of getAlternativeObjectIds(game.objectID)) {
|
||||||
gameAchievementFiles.push(
|
gameAchievementFiles.push(
|
||||||
|
@ -197,39 +228,48 @@ export class AchievementWatcherManager {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const achievementFile of gameAchievementFiles) {
|
return this.preProcessGameAchievementFiles(game, gameAchievementFiles);
|
||||||
const parsedAchievements = parseAchievementFile(
|
|
||||||
achievementFile.filePath,
|
|
||||||
achievementFile.type
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const currentStat = fs.statSync(achievementFile.filePath);
|
|
||||||
fileStats.set(achievementFile.filePath, currentStat.mtimeMs);
|
|
||||||
} catch {
|
|
||||||
fileStats.set(achievementFile.filePath, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsedAchievements.length) {
|
|
||||||
unlockedAchievements.push(...parsedAchievements);
|
|
||||||
|
|
||||||
achievementsLogger.log(
|
|
||||||
"Achievement file for",
|
|
||||||
game.title,
|
|
||||||
achievementFile.filePath,
|
|
||||||
parsedAchievements
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await mergeAchievements(
|
|
||||||
game.objectID,
|
|
||||||
"steam",
|
|
||||||
unlockedAchievements,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private static preSearchAchievementsWithWine = async () => {
|
||||||
|
const games = await gameRepository.find({
|
||||||
|
where: {
|
||||||
|
isDeleted: false,
|
||||||
|
winePrefixPath: Not(IsNull()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
games.map((game) => {
|
||||||
|
gameAchievementRepository
|
||||||
|
.findOne({
|
||||||
|
where: { objectId: game.objectID, shop: game.shop },
|
||||||
|
})
|
||||||
|
.then((localAchievements) => {
|
||||||
|
if (!localAchievements || !localAchievements.achievements) {
|
||||||
|
getGameAchievementData(game.objectID, game.shop);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const gameAchievementFiles = findAchievementFiles(game);
|
||||||
|
const achievementFileInsideDirectory =
|
||||||
|
findAchievementFileInExecutableDirectory(game);
|
||||||
|
|
||||||
|
gameAchievementFiles.push(...achievementFileInsideDirectory);
|
||||||
|
|
||||||
|
return this.preProcessGameAchievementFiles(game, gameAchievementFiles);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
public static preSearchAchievements = async () => {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
await this.preSearchAchievementsWindows();
|
||||||
|
} else {
|
||||||
|
await this.preSearchAchievementsWithWine();
|
||||||
|
}
|
||||||
|
|
||||||
this.hasFinishedMergingWithRemote = true;
|
this.hasFinishedMergingWithRemote = true;
|
||||||
};
|
};
|
|
@ -11,7 +11,8 @@ import { getUnlockedAchievements } from "@main/events/user/get-unlocked-achievem
|
||||||
const saveAchievementsOnLocal = async (
|
const saveAchievementsOnLocal = async (
|
||||||
objectId: string,
|
objectId: string,
|
||||||
shop: GameShop,
|
shop: GameShop,
|
||||||
achievements: any[]
|
achievements: any[],
|
||||||
|
sendUpdateEvent: boolean
|
||||||
) => {
|
) => {
|
||||||
return gameAchievementRepository
|
return gameAchievementRepository
|
||||||
.upsert(
|
.upsert(
|
||||||
|
@ -23,6 +24,8 @@ const saveAchievementsOnLocal = async (
|
||||||
["objectId", "shop"]
|
["objectId", "shop"]
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
if (!sendUpdateEvent) return;
|
||||||
|
|
||||||
return getUnlockedAchievements(objectId, shop)
|
return getUnlockedAchievements(objectId, shop)
|
||||||
.then((achievements) => {
|
.then((achievements) => {
|
||||||
WindowManager.mainWindow?.webContents.send(
|
WindowManager.mainWindow?.webContents.send(
|
||||||
|
@ -133,13 +136,24 @@ export const mergeAchievements = async (
|
||||||
return saveAchievementsOnLocal(
|
return saveAchievementsOnLocal(
|
||||||
response.objectId,
|
response.objectId,
|
||||||
response.shop,
|
response.shop,
|
||||||
response.achievements
|
response.achievements,
|
||||||
|
publishNotification
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
return saveAchievementsOnLocal(objectId, shop, mergedLocalAchievements);
|
return saveAchievementsOnLocal(
|
||||||
|
objectId,
|
||||||
|
shop,
|
||||||
|
mergedLocalAchievements,
|
||||||
|
publishNotification
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return saveAchievementsOnLocal(objectId, shop, mergedLocalAchievements);
|
return saveAchievementsOnLocal(
|
||||||
|
objectId,
|
||||||
|
shop,
|
||||||
|
mergedLocalAchievements,
|
||||||
|
publishNotification
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
// import { omit } from "lodash-es";
|
// import { omit } from "lodash-es";
|
||||||
import { appVersion } from "@main/constants";
|
import { appVersion } from "@main/constants";
|
||||||
|
import { omit } from "lodash-es";
|
||||||
|
|
||||||
interface HydraApiOptions {
|
interface HydraApiOptions {
|
||||||
needsAuth?: boolean;
|
needsAuth?: boolean;
|
||||||
|
@ -24,7 +25,7 @@ export class HydraApi {
|
||||||
private static instance: AxiosInstance;
|
private static instance: AxiosInstance;
|
||||||
|
|
||||||
private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes
|
private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes
|
||||||
private static readonly ADD_LOG_INTERCEPTOR = true;
|
private static readonly ADD_LOG_INTERCEPTOR = false;
|
||||||
|
|
||||||
private static secondsToMilliseconds = (seconds: number) => seconds * 1000;
|
private static secondsToMilliseconds = (seconds: number) => seconds * 1000;
|
||||||
|
|
||||||
|
@ -109,59 +110,59 @@ export class HydraApi {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.ADD_LOG_INTERCEPTOR) {
|
if (this.ADD_LOG_INTERCEPTOR) {
|
||||||
// this.instance.interceptors.request.use(
|
this.instance.interceptors.request.use(
|
||||||
// (request) => {
|
(request) => {
|
||||||
// logger.log(" ---- REQUEST -----");
|
logger.log(" ---- REQUEST -----");
|
||||||
// const data = Array.isArray(request.data)
|
const data = Array.isArray(request.data)
|
||||||
// ? request.data
|
? request.data
|
||||||
// : omit(request.data, ["refreshToken"]);
|
: omit(request.data, ["refreshToken"]);
|
||||||
// logger.log(request.method, request.url, request.params, data);
|
logger.log(request.method, request.url, request.params, data);
|
||||||
// return request;
|
return request;
|
||||||
// },
|
},
|
||||||
// (error) => {
|
(error) => {
|
||||||
// logger.error("request error", error);
|
logger.error("request error", error);
|
||||||
// return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
// }
|
}
|
||||||
// );
|
);
|
||||||
// this.instance.interceptors.response.use(
|
this.instance.interceptors.response.use(
|
||||||
// (response) => {
|
(response) => {
|
||||||
// logger.log(" ---- RESPONSE -----");
|
logger.log(" ---- RESPONSE -----");
|
||||||
// const data = Array.isArray(response.data)
|
const data = Array.isArray(response.data)
|
||||||
// ? response.data
|
? response.data
|
||||||
// : omit(response.data, ["username", "accessToken", "refreshToken"]);
|
: omit(response.data, ["username", "accessToken", "refreshToken"]);
|
||||||
// logger.log(
|
logger.log(
|
||||||
// response.status,
|
response.status,
|
||||||
// response.config.method,
|
response.config.method,
|
||||||
// response.config.url,
|
response.config.url,
|
||||||
// data
|
data
|
||||||
// );
|
);
|
||||||
// return response;
|
return response;
|
||||||
// },
|
},
|
||||||
// (error) => {
|
(error) => {
|
||||||
// logger.error(" ---- RESPONSE ERROR -----");
|
logger.error(" ---- RESPONSE ERROR -----");
|
||||||
// const { config } = error;
|
const { config } = error;
|
||||||
// logger.error(
|
logger.error(
|
||||||
// config.method,
|
config.method,
|
||||||
// config.baseURL,
|
config.baseURL,
|
||||||
// config.url,
|
config.url,
|
||||||
// config.headers,
|
config.headers,
|
||||||
// config.data
|
config.data
|
||||||
// );
|
);
|
||||||
// if (error.response) {
|
if (error.response) {
|
||||||
// logger.error(
|
logger.error(
|
||||||
// "Response",
|
"Response",
|
||||||
// error.response.status,
|
error.response.status,
|
||||||
// error.response.data
|
error.response.data
|
||||||
// );
|
);
|
||||||
// } else if (error.request) {
|
} else if (error.request) {
|
||||||
// logger.error("Request", error.request);
|
logger.error("Request", error.request);
|
||||||
// } else {
|
} else {
|
||||||
// logger.error("Error", error.message);
|
logger.error("Error", error.message);
|
||||||
// }
|
}
|
||||||
// logger.error(" ----- END RESPONSE ERROR -------");
|
logger.error(" ----- END RESPONSE ERROR -------");
|
||||||
// return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
// }
|
}
|
||||||
// );
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userAuth = await userAuthRepository.findOne({
|
const userAuth = await userAuthRepository.findOne({
|
||||||
|
|
|
@ -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 { AchievementWatcherManager } from "../achievements/AchievementWatcherManager";
|
import { AchievementWatcherManager } from "../achievements/achievement-watcher-manager";
|
||||||
|
|
||||||
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 AchievementWatcherManager.preSearchAchievements();
|
AchievementWatcherManager.preSearchAchievements();
|
||||||
|
|
||||||
if (WindowManager.mainWindow)
|
if (WindowManager.mainWindow)
|
||||||
WindowManager.mainWindow.webContents.send("on-library-batch-complete");
|
WindowManager.mainWindow.webContents.send("on-library-batch-complete");
|
||||||
|
|
|
@ -1,7 +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 { AchievementWatcherManager } from "./achievements/AchievementWatcherManager";
|
import { AchievementWatcherManager } from "./achievements/achievement-watcher-manager";
|
||||||
|
|
||||||
export const startMainLoop = async () => {
|
export const startMainLoop = async () => {
|
||||||
// eslint-disable-next-line no-constant-condition
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useNavigate } from "react-router-dom";
|
||||||
import { PeopleIcon } from "@primer/octicons-react";
|
import { PeopleIcon } from "@primer/octicons-react";
|
||||||
import * as styles from "./sidebar-profile.css";
|
import * as styles from "./sidebar-profile.css";
|
||||||
import { useAppSelector, useUserDetails } from "@renderer/hooks";
|
import { useAppSelector, useUserDetails } from "@renderer/hooks";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal";
|
import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal";
|
||||||
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
||||||
|
@ -13,8 +13,6 @@ const LONG_POLLING_INTERVAL = 60_000;
|
||||||
export function SidebarProfile() {
|
export function SidebarProfile() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const pollingInterval = useRef<NodeJS.Timeout | null>(null);
|
|
||||||
|
|
||||||
const { t } = useTranslation("sidebar");
|
const { t } = useTranslation("sidebar");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -36,14 +34,12 @@ export function SidebarProfile() {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pollingInterval.current = setInterval(() => {
|
const pollingInterval = setInterval(() => {
|
||||||
syncFriendRequests();
|
syncFriendRequests();
|
||||||
}, LONG_POLLING_INTERVAL);
|
}, LONG_POLLING_INTERVAL);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (pollingInterval.current) {
|
clearInterval(pollingInterval);
|
||||||
clearInterval(pollingInterval.current);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [syncFriendRequests]);
|
}, [syncFriendRequests]);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue