[bugfix] use yauzl for extracting zips

This commit is contained in:
silentrald
2024-11-08 00:16:35 +08:00
parent 7c6c6147a6
commit 68559add5d
11 changed files with 268 additions and 115 deletions
@@ -30,6 +30,7 @@ import { MapInfo } from "shared/models/maps/info/map-info.model";
import { parseMapInfoDat } from "shared/parsers/maps/map-info.parser";
import { CustomError } from "shared/models/exceptions/custom-error.class";
import { tryit } from "shared/helpers/error.helpers";
import { extractZip } from "main/helpers/zip.helpers";
export class LocalMapsManagerService {
private static instance: LocalMapsManagerService;
@@ -169,17 +170,13 @@ export class LocalMapsManagerService {
return getUrlsAndReturn(mapInfo, hash, mapPath);
}
private async downloadMapZip(zipUrl: string): Promise<{ zip: StreamZip.StreamZipAsync; zipPath: string }> {
private async downloadMapZip(zipUrl: string): Promise<string> {
const fileName = `${path.basename(zipUrl, ".zip")}-${crypto.randomUUID()}.zip`;
const tempPath = this.utils.getTempPath();
await ensureFolderExist(this.utils.getTempPath());
const dest = path.join(tempPath, fileName);
const zipPath = (await lastValueFrom(this.reqService.downloadFile(zipUrl, dest))).data;
const zip = new StreamZip.async({ file: zipPath });
return { zip, zipPath };
return (await lastValueFrom(this.reqService.downloadFile(zipUrl, dest))).data;
}
public getMaps(version?: BSVersion): Observable<BsmLocalMapsProgress> {
@@ -454,16 +451,8 @@ export class LocalMapsManagerService {
return installedMap;
}
const { zip, zipPath } = await this.downloadMapZip(zipUrl);
if (!zip) {
throw new Error(`Cannot download ${zipUrl}`);
}
await ensureFolderExist(mapPath);
await zip.extract(null, mapPath);
await zip.close();
const zipPath = await this.downloadMapZip(zipUrl);
await extractZip(zipPath, mapPath);
await unlink(zipPath);
const localMap = await this.loadMapInfoFromPath(mapPath);
@@ -8,17 +8,18 @@ import { RequestService } from "../request.service";
import { spawn } from "child_process";
import { BS_EXECUTABLE } from "../../constants";
import log from "electron-log";
import { deleteFolder, pathExist, Progression, unlinkPath } from "../../helpers/fs.helpers";
import { deleteFolder, ensureFolderExist, pathExist, Progression, unlinkPath } from "../../helpers/fs.helpers";
import { lastValueFrom, Observable } from "rxjs";
import JSZip from "jszip";
import { extractZip } from "../../helpers/zip.helpers";
import { extractZip, validateZip } from "../../helpers/zip.helpers";
import recursiveReadDir from "recursive-readdir";
import { sToMs } from "../../../shared/helpers/time.helpers";
import { ensureDir, pathExistsSync } from "fs-extra";
import { pathExistsSync, unlinkSync } from "fs-extra";
import { CustomError } from "shared/models/exceptions/custom-error.class";
import { popElement } from "shared/helpers/array.helpers";
import { LinuxService } from "../linux.service";
import { tryit } from "shared/helpers/error.helpers";
import { UtilsService } from "../utils.service";
import crypto from "crypto";
export class BsModsManagerService {
private static instance: BsModsManagerService;
@@ -27,6 +28,7 @@ export class BsModsManagerService {
private readonly bsLocalService: BSLocalVersionService;
private readonly linuxService: LinuxService;
private readonly requestService: RequestService;
private readonly utilsService: UtilsService;
private manifestMatches: Mod[];
@@ -42,6 +44,7 @@ export class BsModsManagerService {
this.bsLocalService = BSLocalVersionService.getInstance();
this.linuxService = LinuxService.getInstance();
this.requestService = RequestService.getInstance();
this.utilsService = UtilsService.getInstance();
}
private async getModFromHash(hash: string): Promise<Mod> {
@@ -110,26 +113,22 @@ export class BsModsManagerService {
return this.beatModsApi.getModByHash(injectorMd5);
}
private async downloadZip(zipUrl: string): Promise<JSZip> {
private async downloadZip(zipUrl: string): Promise<string> {
zipUrl = path.join(this.beatModsApi.BEAT_MODS_URL, zipUrl);
log.info("Download mod zip", zipUrl);
const buffer = await lastValueFrom(this.requestService.downloadBuffer(zipUrl))
.then(progress => progress.data)
.catch(e => {
log.error("ZIP", "Error while downloading zip", e);
return undefined;
});
const fileName = `${path.basename(zipUrl, ".zip")}-${crypto.randomUUID()}.zip`;
const tempPath = this.utilsService.getTempPath();
const destination = path.join(tempPath, fileName);
if (!buffer) {
try {
await ensureFolderExist(tempPath);
return (await lastValueFrom(this.requestService.downloadFile(zipUrl, destination))).data;
} catch (error) {
log.error("Could not download zip file at", zipUrl);
return null;
}
return JSZip.loadAsync(buffer).catch(e => {
log.error("ZIP", "Error while loading zip", e);
return null;
});
}
private async executeBSIPA(version: BSVersion, args: string[]): Promise<boolean> {
@@ -200,44 +199,31 @@ export class BsModsManagerService {
}
log.info("Start download mod zip", mod.name, download.url);
const zip = await this.downloadZip(download.url);
log.info("Mod zip download end", mod.name, download.url, !!zip);
if (!zip) {
const zipPath = await this.downloadZip(download.url);
log.info("Mod zip download end", mod.name, download.url, !!zipPath);
if (!zipPath) {
return false;
}
const crypto = require("crypto");
const { files } = zip;
const checkedEntries = (
await Promise.all(
Object.values(files).map(async entry => {
const data = await entry.async("nodebuffer");
const entryMd5 = crypto.createHash("md5").update(data).digest("hex");
return download.hashMd5.some(md5 => md5.hash === entryMd5) ? entry : undefined;
})
).catch(e => {
log.error("Error while checking mod zip entries", mod.name, e);
throw e;
})
).filter(entry => !!entry);
if (checkedEntries.length !== download.hashMd5.length) {
if (!(await validateZip(zipPath, download.hashMd5))) {
return false;
}
const verionPath = await this.bsLocalService.getVersionPath(version);
const versionPath = await this.bsLocalService.getVersionPath(version);
const isBSIPA = mod.name.toLowerCase() === "bsipa";
const destDir = isBSIPA ? verionPath : path.join(verionPath, ModsInstallFolder.PENDING);
const destDir = isBSIPA ? versionPath : path.join(versionPath, ModsInstallFolder.PENDING);
await ensureDir(destDir);
log.info("Start extracting mod zip", mod.name, "to", destDir);
const extracted = await extractZip(zip, destDir)
const extracted = await extractZip(zipPath, destDir)
.then(() => true)
.catch(e => {
log.error("Error while extracting mod zip", e);
return false;
})
.finally(() => {
if (pathExistsSync(zipPath)) {
unlinkSync(zipPath);
}
});
log.info("Mod zip extraction end", mod.name, "to", destDir, "success:", extracted);