Merge pull request #755 from Zagrios/feature/outdated-mods-notification

[feature] Outdated mods notification
This commit is contained in:
MathieuG-P
2025-01-26 15:21:21 +01:00
committed by GitHub
15 changed files with 239 additions and 92 deletions
@@ -3,6 +3,7 @@ import { BbmFullMod, BbmMod, BbmModVersion, BbmPlatform } from "../../../shared/
import { RequestService } from "../request.service";
import { BsStore } from "../../../shared/models/bs-store.enum";
import log from "electron-log"
import { tryit } from "shared/helpers/error.helpers";
export class BeatModsApiService {
private static instance: BeatModsApiService;
@@ -59,17 +60,42 @@ export class BeatModsApiService {
});
}
public getModByHash(hash: string): Promise<BbmModVersion|undefined> {
if (this.modsHashCache.has(hash)) {
return Promise.resolve(this.modsHashCache.get(hash));
public async getModByHash<T extends string>(hashs: T[]): Promise<Record<T, BbmModVersion>> {
const getModsFromCache = (hashs: T[]): Record<T, BbmModVersion> => {
const mods = {} as Record<T, BbmModVersion>;
for (const hash of hashs) {
const mod = this.modsHashCache.get(hash);
if(mod){
mods[hash] = mod;
}
}
return mods;
}
return this.requestService.getJSON<{ modVersions: BbmModVersion[] }>(`${this.MODS_REPO_API_URL}/hashlookup?hash=${hash}`).then(({ data }) => {
this.updateModsHashCache(data?.modVersions ?? []);
return data?.modVersions?.at(0);
}).catch((e): undefined => {
log.error(`Failed to get mod by hash: ${hash}`, e);
const mods = getModsFromCache(hashs);
const missingHashs = hashs.filter(hash => !mods[hash]);
if(!missingHashs.length){
return mods;
}
const url = new URL(`${this.MODS_REPO_API_URL}/hashlookup`);
missingHashs.forEach(hash => url.searchParams.append("hash", hash));
const res = await tryit(() => this.requestService.getJSON<{ modVersions: BbmModVersion[] }>(url.toString()));
if(res.error){
log.error(`Failed to get mod by hashes`, res.error);
return undefined;
});
}
this.updateModsHashCache(res.result?.data?.modVersions ?? []);
const missingMods = getModsFromCache(missingHashs);
return {...mods, ...missingMods};
}
}
@@ -19,6 +19,7 @@ import crypto from "crypto";
import { BsmZipExtractor } from "main/models/bsm-zip-extractor.class";
import { BsmShellLog, bsmSpawn } from "main/helpers/os.helpers";
import { BbmFullMod, BbmModVersion, ExternalMod } from "../../../shared/models/mods/mod.interface";
import { Stats } from "fs";
export class BsModsManagerService {
private static instance: BsModsManagerService;
@@ -28,8 +29,6 @@ export class BsModsManagerService {
private readonly linuxService: LinuxService;
private readonly requestService: RequestService;
private manifestMatches: BbmModVersion[];
public static getInstance(): BsModsManagerService {
if (!BsModsManagerService.instance) {
BsModsManagerService.instance = new BsModsManagerService();
@@ -44,14 +43,9 @@ export class BsModsManagerService {
this.requestService = RequestService.getInstance();
}
private async getModFromHash(hash: string): Promise<BbmModVersion | undefined> {
const mod = await this.beatModsApi.getModByHash(hash);
if(mod?.contentHashes?.some(content => content.path.includes("IPA.exe"))){
return undefined;
}
return mod;
private async getModsFromHashes(hashes: string[]): Promise<BbmModVersion[] | undefined> {
const mods = await this.beatModsApi.getModByHash(hashes)
return Object.values(mods).filter(mod => !mod.contentHashes.some(content => content.path.includes("IPA")));
}
@@ -63,40 +57,16 @@ export class BsModsManagerService {
return [];
}
const files = await recursiveReadDir(modsPath);
const ignoreFunc = (file: string, stats: Stats): boolean => {
if(!stats.isFile()) { return true; }
const ext = path.extname(file);
return !(ext === ".dll" || ext === ".exe" || ext === ".manifest");
}
const promises = files.map(async filePath => {
const ext = path.extname(filePath);
const files = await recursiveReadDir(modsPath, [ignoreFunc]);
const hashes = await Promise.all(files.map(file => md5File(file)));
if (ext !== ".dll" && ext !== ".exe" && ext !== ".manifest") {
return undefined;
}
const hash = await md5File(filePath);
const mod = await this.getModFromHash(hash);
if (!mod) {
return undefined;
}
if (ext === ".manifest") {
this.manifestMatches.push(mod);
return undefined;
}
if (filePath.toLowerCase().includes("libs")) {
const manifestIndex = this.manifestMatches.findIndex(m => m.id === mod.id);
if (manifestIndex < 0) {
return undefined;
}
this.manifestMatches.splice(manifestIndex, 1);
}
return mod;
});
const mods = await Promise.all(promises);
const mods = await this.getModsFromHashes(hashes);
return mods.filter(Boolean);
}
@@ -107,7 +77,7 @@ export class BsModsManagerService {
return undefined;
}
const injectorMd5 = await md5File(injectorPath);
return this.beatModsApi.getModByHash(injectorMd5);
return (await this.beatModsApi.getModByHash([injectorMd5]))[injectorMd5];
}
private async downloadZip(zipUrl: string): Promise<BsmZipExtractor> {
@@ -338,12 +308,10 @@ export class BsModsManagerService {
}
public async getInstalledMods(version: BSVersion): Promise<BbmModVersion[]> {
this.manifestMatches = [];
const bsipa = await this.getBsipaInstalled(version);
const pluginsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.PLUGINS), this.getModsInDir(version, ModsInstallFolder.PLUGINS_PENDING)]);
const libsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.LIBS), this.getModsInDir(version, ModsInstallFolder.LIBS_PENDING)]);
const pluginsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.PLUGINS_PENDING), this.getModsInDir(version, ModsInstallFolder.PLUGINS)]);
const libsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.LIBS_PENDING), this.getModsInDir(version, ModsInstallFolder.LIBS)]);
const dirMods = pluginsMods.flat().concat(libsMods.flat());