mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[bugfix] some adjutments + some fixes + add zip tests
This commit is contained in:
@@ -59,7 +59,6 @@ module.exports = {
|
||||
"react/function-component-definition": "off",
|
||||
"jsx-a11y/control-has-associated-label": "off",
|
||||
"react/button-has-type": "off",
|
||||
"no-labels": ["error", { "allowLoop": true }],
|
||||
"max-classes-per-file": "off",
|
||||
},
|
||||
parserOptions: {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+192
-18
@@ -1,16 +1,18 @@
|
||||
import path from "path";
|
||||
import { mkdir, pathExistsSync, readFile, rm } from "fs-extra";
|
||||
import { YauzlZip } from "main/models/yauzl-zip.class";
|
||||
import { BsmZipExtractor } from "main/models/bsm-zip-extractor.class";
|
||||
|
||||
const TEST_FOLDER = path.resolve(__dirname, "../../..", "assets", "tests");
|
||||
const TEST_FOLDER = path.resolve(__dirname, "..", "assets", "zip");
|
||||
const STANDARD_ZIP = path.join(TEST_FOLDER, "standard.zip");
|
||||
const WINDOWS_LEGACY_MAP_ZIP = path.join(TEST_FOLDER, "windows_legacy.zip");
|
||||
const SUBFOLDERS_ZIP = path.join(TEST_FOLDER, "subfolders.zip");
|
||||
const MANIFEST_ZIP = path.join(TEST_FOLDER, "manifest.zip");
|
||||
const EMPTY_ZIP = path.join(TEST_FOLDER, "empty.zip");
|
||||
const UNICODE_ZIP = path.join(TEST_FOLDER, "unicode.zip");
|
||||
const DESTINATION_FOLDER = path.join(TEST_FOLDER, "out");
|
||||
|
||||
describe("Test YauzlZip class", () => {
|
||||
let zip: YauzlZip;
|
||||
describe("Test BsmZipExtractor class", () => {
|
||||
let zip: BsmZipExtractor;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (pathExistsSync(DESTINATION_FOLDER)) {
|
||||
@@ -19,9 +21,25 @@ describe("Test YauzlZip class", () => {
|
||||
await mkdir(DESTINATION_FOLDER);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (zip) {
|
||||
zip.close();
|
||||
}
|
||||
if (pathExistsSync(DESTINATION_FOLDER)) {
|
||||
await rm(DESTINATION_FOLDER, { recursive: true, force: true });
|
||||
}
|
||||
await mkdir(DESTINATION_FOLDER);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (pathExistsSync(DESTINATION_FOLDER)) {
|
||||
await rm(DESTINATION_FOLDER, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("Extract standard zip", async () => {
|
||||
|
||||
zip = await YauzlZip.fromPath(STANDARD_ZIP);
|
||||
zip = await BsmZipExtractor.fromPath(STANDARD_ZIP);
|
||||
const res = await zip.extract(DESTINATION_FOLDER);
|
||||
|
||||
expect(res.sort()).toEqual([
|
||||
@@ -53,7 +71,7 @@ describe("Test YauzlZip class", () => {
|
||||
|
||||
it("Extract map zips using back slashes", async () => {
|
||||
|
||||
zip = await YauzlZip.fromPath(WINDOWS_LEGACY_MAP_ZIP);
|
||||
zip = await BsmZipExtractor.fromPath(WINDOWS_LEGACY_MAP_ZIP);
|
||||
const res = await zip.extract(DESTINATION_FOLDER);
|
||||
|
||||
expect(res.sort()).toEqual([
|
||||
@@ -100,7 +118,7 @@ describe("Test YauzlZip class", () => {
|
||||
|
||||
it("Read Zip with multiple subfolders", async () => {
|
||||
|
||||
const zip = await YauzlZip.fromPath(SUBFOLDERS_ZIP);
|
||||
const zip = await BsmZipExtractor.fromPath(SUBFOLDERS_ZIP);
|
||||
const res = await zip.extract(DESTINATION_FOLDER);
|
||||
|
||||
expect(res.sort()).toEqual([
|
||||
@@ -129,7 +147,7 @@ describe("Test YauzlZip class", () => {
|
||||
it("Read manifest.json from zip file", async () => {
|
||||
|
||||
const zipBuffer = await readFile(MANIFEST_ZIP);
|
||||
zip = await YauzlZip.fromBuffer(zipBuffer);
|
||||
zip = await BsmZipExtractor.fromBuffer(zipBuffer);
|
||||
const buffer = await (await zip.findEntry(entry => entry.fileName === "manifest.json")).read();
|
||||
const manifest = JSON.parse(buffer.toString());
|
||||
expect(manifest).toBeTruthy();
|
||||
@@ -140,16 +158,172 @@ describe("Test YauzlZip class", () => {
|
||||
expect(manifest.isCore).toBe(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (zip) {
|
||||
zip.close();
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (pathExistsSync(DESTINATION_FOLDER)) {
|
||||
await rm(DESTINATION_FOLDER, { recursive: true, force: true });
|
||||
}
|
||||
it("Should find an entry using findEntry", async () => {
|
||||
zip = await BsmZipExtractor.fromPath(STANDARD_ZIP);
|
||||
const entry = await zip.findEntry(entry => entry.fileName === "file_1.2.txt");
|
||||
|
||||
expect(entry).toBeTruthy();
|
||||
expect(entry.fileName).toBe("file_1.2.txt");
|
||||
|
||||
const contentBuffer = await entry.read();
|
||||
const content = contentBuffer.toString();
|
||||
|
||||
expect(content).toContain("This is file 1.2"); // Assurez-vous que le contenu correspond
|
||||
});
|
||||
|
||||
})
|
||||
it("Should filter entries using filterEntries", async () => {
|
||||
zip = await BsmZipExtractor.fromPath(STANDARD_ZIP);
|
||||
const entries = await zip.filterEntries(entry => !entry.isDirectory && entry.fileName.startsWith("folder_1.1/"));
|
||||
|
||||
expect(entries.length).toBe(2);
|
||||
const fileNames = entries.map(entry => entry.fileName).sort();
|
||||
|
||||
expect(fileNames).toEqual([
|
||||
"folder_1.1/file_2.1.txt",
|
||||
"folder_1.1/file_2.2.txt",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Should get an entry by file name using getEntry", async () => {
|
||||
zip = await BsmZipExtractor.fromPath(STANDARD_ZIP);
|
||||
const entry = await zip.getEntry("file_1.3.txt");
|
||||
|
||||
expect(entry).toBeTruthy();
|
||||
expect(entry.fileName).toBe("file_1.3.txt");
|
||||
|
||||
const contentBuffer = await entry.read();
|
||||
const content = contentBuffer.toString();
|
||||
|
||||
expect(content).toContain("This is file 1.3"); // Assurez-vous que le contenu correspond
|
||||
});
|
||||
|
||||
it("Should extract only specified entries using entriesNames option", async () => {
|
||||
zip = await BsmZipExtractor.fromPath(STANDARD_ZIP);
|
||||
const res = await zip.extract(DESTINATION_FOLDER, { entriesNames: ["file_1.2.txt", "folder_1.1/file_2.1.txt"] });
|
||||
|
||||
expect(res.sort()).toEqual([
|
||||
"file_1.2.txt",
|
||||
"folder_1.1/",
|
||||
"folder_1.1/file_2.1.txt",
|
||||
].sort());
|
||||
|
||||
for (const file of [
|
||||
"file_1.2.txt",
|
||||
"folder_1.1/file_2.1.txt",
|
||||
]) {
|
||||
expect(pathExistsSync(path.join(DESTINATION_FOLDER, file)))
|
||||
.toBe(true);
|
||||
}
|
||||
|
||||
// Vérifiez que les autres fichiers ne sont pas extraits
|
||||
expect(pathExistsSync(path.join(DESTINATION_FOLDER, "file_1.3.txt")))
|
||||
.toBe(false);
|
||||
expect(pathExistsSync(path.join(DESTINATION_FOLDER, "folder_1.1/file_2.2.txt")))
|
||||
.toBe(false);
|
||||
});
|
||||
|
||||
it("Should not extract any files if abortToken is already aborted", async () => {
|
||||
zip = await BsmZipExtractor.fromPath(STANDARD_ZIP);
|
||||
const abortController = new AbortController();
|
||||
|
||||
// Abortez avant d'appeler extract
|
||||
abortController.abort();
|
||||
|
||||
const res = await zip.extract(DESTINATION_FOLDER, { abortToken: abortController });
|
||||
|
||||
expect(res.length).toBe(0);
|
||||
|
||||
// Vérifiez qu'aucun fichier n'a été extrait
|
||||
expect(pathExistsSync(path.join(DESTINATION_FOLDER, "file_1.2.txt")))
|
||||
.toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it("Should throw an error when opening a non-existent zip file", async () => {
|
||||
await expect(BsmZipExtractor.fromPath("non_existent.zip"))
|
||||
.rejects
|
||||
.toThrow();
|
||||
});
|
||||
|
||||
it("Should handle empty zip file correctly", async () => {
|
||||
zip = await BsmZipExtractor.fromPath(EMPTY_ZIP);
|
||||
const entries = [];
|
||||
|
||||
for await (const entry of zip.entries()) {
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
expect(entries.length).toBe(0);
|
||||
|
||||
const res = await zip.extract(DESTINATION_FOLDER);
|
||||
expect(res.length).toBe(0);
|
||||
});
|
||||
|
||||
it("Should return the same entries on multiple calls to entries()", async () => {
|
||||
zip = await BsmZipExtractor.fromPath(STANDARD_ZIP);
|
||||
|
||||
const entries1 = [];
|
||||
for await (const entry of zip.entries()) {
|
||||
entries1.push(entry);
|
||||
}
|
||||
|
||||
expect(entries1.length).toBe(5); // Basé sur le contenu de STANDARD_ZIP
|
||||
|
||||
const entries2 = [];
|
||||
for await (const entry of zip.entries()) {
|
||||
entries2.push(entry);
|
||||
}
|
||||
|
||||
expect(entries2.length).toBe(5);
|
||||
expect(entries2.map(e => e.fileName).sort()).toEqual(entries1.map(e => e.fileName).sort());
|
||||
});
|
||||
|
||||
it("Should not be able to read entries after zip is closed", async () => {
|
||||
zip = await BsmZipExtractor.fromPath(STANDARD_ZIP);
|
||||
|
||||
zip.close();
|
||||
|
||||
await expect(zip.entries().next()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("Should handle zip file from buffer", async () => {
|
||||
const zipBuffer = await readFile(STANDARD_ZIP);
|
||||
zip = await BsmZipExtractor.fromBuffer(zipBuffer);
|
||||
|
||||
const entries = [];
|
||||
for await (const entry of zip.entries()) {
|
||||
entries.push(entry.fileName);
|
||||
}
|
||||
|
||||
expect(entries.sort()).toEqual([
|
||||
"file_1.2.txt",
|
||||
"file_1.3.txt",
|
||||
"folder_1.1/",
|
||||
"folder_1.1/file_2.1.txt",
|
||||
"folder_1.1/file_2.2.txt",
|
||||
].sort());
|
||||
});
|
||||
|
||||
it("Should read and extract files with Unicode filenames", async () => {
|
||||
zip = await BsmZipExtractor.fromPath(UNICODE_ZIP);
|
||||
|
||||
const entries = [];
|
||||
for await (const entry of zip.entries()) {
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
expect(entries.map(e => e.fileName)).toContain("こんにちは.txt"); // "Bonjour" en japonais
|
||||
|
||||
const res = await zip.extract(DESTINATION_FOLDER);
|
||||
expect(res).toContain("こんにちは.txt");
|
||||
expect(pathExistsSync(path.join(DESTINATION_FOLDER, "こんにちは.txt")))
|
||||
.toBe(true);
|
||||
|
||||
});
|
||||
|
||||
it("Should throw an error when reading a corrupted zip file", async () => {
|
||||
const CORRUPTED_ZIP = path.join(TEST_FOLDER, "corrupted.zip");
|
||||
await expect(BsmZipExtractor.fromPath(CORRUPTED_ZIP)).rejects.toThrow(/.*[Error: Invalid comment length].*/)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from "path";
|
||||
import { Readable } from "stream";
|
||||
import yauzl, { ZipFile, Options, Entry } from "yauzl"
|
||||
|
||||
export class YauzlZip {
|
||||
export class BsmZipExtractor {
|
||||
|
||||
private static readonly YAUZL_OPEN_OPTIONS: Options = {
|
||||
lazyEntries: true,
|
||||
@@ -11,36 +11,36 @@ export class YauzlZip {
|
||||
autoClose: false,
|
||||
};
|
||||
|
||||
public static fromPath(path: string): Promise<YauzlZip> {
|
||||
public static fromPath(path: string): Promise<BsmZipExtractor> {
|
||||
return new Promise((resolve, reject) => {
|
||||
yauzl.open(path, YauzlZip.YAUZL_OPEN_OPTIONS, (error: Error, zip: yauzl.ZipFile) => {
|
||||
yauzl.open(path, BsmZipExtractor.YAUZL_OPEN_OPTIONS, (error: Error, zip: yauzl.ZipFile) => {
|
||||
if (error) return reject(error);
|
||||
resolve(new YauzlZip(zip));
|
||||
resolve(new BsmZipExtractor(zip));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static fromBuffer(buffer: Buffer): Promise<YauzlZip> {
|
||||
public static fromBuffer(buffer: Buffer): Promise<BsmZipExtractor> {
|
||||
return new Promise((resolve, reject) => {
|
||||
yauzl.fromBuffer(buffer, YauzlZip.YAUZL_OPEN_OPTIONS, (error: Error, zip: yauzl.ZipFile) => {
|
||||
yauzl.fromBuffer(buffer, BsmZipExtractor.YAUZL_OPEN_OPTIONS, (error: Error, zip: yauzl.ZipFile) => {
|
||||
if (error) return reject(error);
|
||||
resolve(new YauzlZip(zip));
|
||||
resolve(new BsmZipExtractor(zip));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private readonly zip: ZipFile;
|
||||
private readonly entriesMap = new Map<string, YauzlZipEntry>();
|
||||
private readonly entriesMap = new Map<string, BsmZipExtractorEntry>();
|
||||
|
||||
private constructor(zip: ZipFile) {
|
||||
this.zip = zip;
|
||||
}
|
||||
|
||||
private async readEntry(): Promise<YauzlZipEntry> {
|
||||
private async readEntry(): Promise<BsmZipExtractorEntry> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onEntry = (entry: Entry) => {
|
||||
cleanup();
|
||||
resolve(new YauzlZipEntry({ entry, zip: this.zip }));
|
||||
resolve(new BsmZipExtractorEntry({ entry, zip: this.zip }));
|
||||
};
|
||||
|
||||
const onEnd = () => {
|
||||
@@ -68,13 +68,13 @@ export class YauzlZip {
|
||||
|
||||
// The first time this method is called, it will read all the entries and store them in a map
|
||||
// The next times it will return the entries from the map to avoid reopening the zip file again
|
||||
public async *entries(): AsyncGenerator<YauzlZipEntry> {
|
||||
public async *entries(): AsyncGenerator<BsmZipExtractorEntry> {
|
||||
|
||||
for (const entry of Array.from(this.entriesMap.values())) {
|
||||
yield entry;
|
||||
}
|
||||
|
||||
let entry: YauzlZipEntry = await this.readEntry();
|
||||
let entry: BsmZipExtractorEntry = await this.readEntry();
|
||||
while (entry) {
|
||||
this.entriesMap.set(entry.fileName, entry);
|
||||
yield entry;
|
||||
@@ -82,7 +82,7 @@ export class YauzlZip {
|
||||
}
|
||||
}
|
||||
|
||||
public async findEntry(func: (entry: YauzlZipEntry) => boolean): Promise<YauzlZipEntry|null> {
|
||||
public async findEntry(func: (entry: BsmZipExtractorEntry) => boolean): Promise<BsmZipExtractorEntry|null> {
|
||||
for await (const entry of this.entries()) {
|
||||
if (func(entry)) {
|
||||
return entry;
|
||||
@@ -91,6 +91,20 @@ export class YauzlZip {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async filterEntries(func: (entry: BsmZipExtractorEntry) => boolean): Promise<BsmZipExtractorEntry[]> {
|
||||
const filtered: BsmZipExtractorEntry[] = [];
|
||||
for await (const entry of this.entries()) {
|
||||
if (func(entry)) {
|
||||
filtered.push(entry);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public async getEntry(fileName: string): Promise<BsmZipExtractorEntry> {
|
||||
return this.entriesMap.get(fileName) ?? this.findEntry(entry => entry.fileName === fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all entries from the zip file to the destination folder
|
||||
* @param destination
|
||||
@@ -149,7 +163,7 @@ export class YauzlZip {
|
||||
|
||||
}
|
||||
|
||||
class YauzlZipEntry {
|
||||
class BsmZipExtractorEntry {
|
||||
|
||||
private readonly entry: Entry;
|
||||
private readonly zip: ZipFile;
|
||||
@@ -6,7 +6,8 @@ import { inflate } from "pako"
|
||||
import { EMPTY, Observable, ReplaySubject, Subscriber, catchError, filter, from, lastValueFrom, mergeMap, scan, share, tap } from "rxjs";
|
||||
import { Progression, hashFile } from "../helpers/fs.helpers";
|
||||
import { OculusDownloaderErrorCodes } from "../../shared/models/bs-version-download/oculus-download.model";
|
||||
import { YauzlZip } from "./yauzl-zip.class";
|
||||
import { BsmZipExtractor } from "./bsm-zip-extractor.class";
|
||||
import { tryit } from "shared/helpers/error.helpers";
|
||||
|
||||
export class OculusDownloader {
|
||||
|
||||
@@ -34,19 +35,23 @@ export class OculusDownloader {
|
||||
const buffer = await this.downloadManifestZip(downloadUrl)
|
||||
.catch(err => CustomError.throw(err, "DOWNLOAD_MANIFEST_FAILED"));
|
||||
|
||||
const manifestName = "manifest.json";
|
||||
const zip = await YauzlZip.fromBuffer(buffer);
|
||||
const entry = await zip.findEntry((entry) => entry.fileName === manifestName);
|
||||
const zip = await BsmZipExtractor.fromBuffer(buffer);
|
||||
const entry = await zip.getEntry("manifest.json");
|
||||
|
||||
if(!entry) {
|
||||
throw new CustomError("Manifest file not found", "MANIFEST_FILE_NOT_FOUND");
|
||||
}
|
||||
|
||||
const manifest = await entry.read();
|
||||
return JSON.parse(manifest.toString())
|
||||
.catch((err: Error) => CustomError.throw(err, "PARSE_MANIFEST_FILE_FAILED"))
|
||||
.finally(() => {
|
||||
zip.close();
|
||||
});
|
||||
zip.close();
|
||||
|
||||
const { result, error } = tryit(() => JSON.parse(manifest.toString()) as OculusManifest);
|
||||
|
||||
if(error){
|
||||
throw CustomError.throw(error, "PARSE_MANIFEST_FILE_FAILED")
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private downloadManifestFile(file: OculusManifestFile, destination: string): Observable<Progression<OculusManifestFile>> {
|
||||
|
||||
@@ -29,7 +29,8 @@ 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 { YauzlZip } from "main/models/yauzl-zip.class";
|
||||
import { BsmZipExtractor } from "main/models/bsm-zip-extractor.class";
|
||||
import { escapeRegExp } from "../../../../shared/helpers/string.helpers";
|
||||
|
||||
export class LocalMapsManagerService {
|
||||
private static instance: LocalMapsManagerService;
|
||||
@@ -327,7 +328,7 @@ export class LocalMapsManagerService {
|
||||
let progress: Progression<BsmLocalMap> = { total: 0, current: 0 };
|
||||
let nbImportedMaps = 0;
|
||||
const abortController = new AbortController();
|
||||
let zip: YauzlZip;
|
||||
let zip: BsmZipExtractor;
|
||||
|
||||
(async () => {
|
||||
const mapsPath = await this.getMapsFolderPath(version);
|
||||
@@ -342,14 +343,9 @@ export class LocalMapsManagerService {
|
||||
|
||||
if(!pathExistsSync(zipPath)) { continue; }
|
||||
|
||||
const mapsFolders: string[] = [];
|
||||
|
||||
zip = await YauzlZip.fromPath(zipPath);
|
||||
for await (const entry of zip.entries()) {
|
||||
if (/(^|\/)[Ii]nfo\.dat$/.test(entry.fileName)) {
|
||||
mapsFolders.push(path.dirname(entry.fileName));
|
||||
}
|
||||
}
|
||||
zip = await BsmZipExtractor.fromPath(zipPath);
|
||||
const mapsFolders = (await zip.filterEntries(entry => /(^|\/)[Ii]nfo\.dat$/.test(entry.fileName)))
|
||||
.map(entry => path.dirname(entry.fileName));
|
||||
|
||||
if (mapsFolders.length === 0) {
|
||||
log.warn("No maps \"info.dat\" found in zip", zipPath);
|
||||
@@ -370,20 +366,19 @@ export class LocalMapsManagerService {
|
||||
log.info("Extracting", `"${zipPath}"`, "into", `"${mapsPath}"`);
|
||||
for (const folder of mapsFolders) {
|
||||
log.info(">", folder);
|
||||
const regex = new RegExp(`^${folder
|
||||
.replaceAll(".", "\\.")
|
||||
.replaceAll("+", "\\+")
|
||||
.replaceAll("(", "\\(")
|
||||
.replaceAll(")", "\\)")
|
||||
.replaceAll("[", "\\[")
|
||||
.replaceAll("]", "\\]")
|
||||
}\\/`);
|
||||
|
||||
await zip.extract(destination, {
|
||||
const regex = new RegExp(`^${escapeRegExp(folder)}\\/`);
|
||||
|
||||
const exported = await zip.extract(destination, {
|
||||
entriesNames: [regex],
|
||||
abortToken: abortController
|
||||
});
|
||||
|
||||
if(exported.length === 0) {
|
||||
log.warn("No files extracted from", folder);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (abortController.signal?.aborted) {
|
||||
break;
|
||||
}
|
||||
@@ -449,7 +444,7 @@ export class LocalMapsManagerService {
|
||||
}
|
||||
|
||||
const zipPath = await this.downloadMapZip(zipUrl);
|
||||
const zip = await YauzlZip.fromPath(zipPath);
|
||||
const zip = await BsmZipExtractor.fromPath(zipPath);
|
||||
await zip.extract(mapPath);
|
||||
zip.close();
|
||||
await unlink(zipPath);
|
||||
|
||||
@@ -8,18 +8,18 @@ import { RequestService } from "../request.service";
|
||||
import { spawn } from "child_process";
|
||||
import { BS_EXECUTABLE } from "../../constants";
|
||||
import log from "electron-log";
|
||||
import { deleteFolder, ensureFolderExist, pathExist, Progression, unlinkPath } from "../../helpers/fs.helpers";
|
||||
import { deleteFolder, pathExist, Progression, unlinkPath } from "../../helpers/fs.helpers";
|
||||
import { lastValueFrom, Observable } from "rxjs";
|
||||
import recursiveReadDir from "recursive-readdir";
|
||||
import { sToMs } from "../../../shared/helpers/time.helpers";
|
||||
import { pathExistsSync, unlinkSync } from "fs-extra";
|
||||
import { pathExistsSync } 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";
|
||||
import { YauzlZip } from "main/models/yauzl-zip.class";
|
||||
import { BsmZipExtractor } from "main/models/bsm-zip-extractor.class";
|
||||
|
||||
export class BsModsManagerService {
|
||||
private static instance: BsModsManagerService;
|
||||
@@ -113,22 +113,23 @@ export class BsModsManagerService {
|
||||
return this.beatModsApi.getModByHash(injectorMd5);
|
||||
}
|
||||
|
||||
private async downloadZip(zipUrl: string): Promise<string> {
|
||||
private async downloadZip(zipUrl: string): Promise<BsmZipExtractor> {
|
||||
zipUrl = path.join(this.beatModsApi.BEAT_MODS_URL, zipUrl);
|
||||
|
||||
log.info("Download mod zip", zipUrl);
|
||||
|
||||
const fileName = `${path.basename(zipUrl, ".zip")}-${crypto.randomUUID()}.zip`;
|
||||
const tempPath = this.utilsService.getTempPath();
|
||||
const destination = path.join(tempPath, fileName);
|
||||
const buffer = await lastValueFrom(this.requestService.downloadBuffer(zipUrl))
|
||||
.then(progress => progress.data)
|
||||
.catch(e => {
|
||||
log.error("ZIP", "Error while downloading zip", e);
|
||||
return undefined;
|
||||
});
|
||||
|
||||
try {
|
||||
await ensureFolderExist(tempPath);
|
||||
return (await lastValueFrom(this.requestService.downloadFile(zipUrl, destination))).data;
|
||||
} catch (error) {
|
||||
log.error("Could not download zip file at", zipUrl);
|
||||
if (!buffer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return BsmZipExtractor.fromBuffer(buffer);
|
||||
}
|
||||
|
||||
private async executeBSIPA(version: BSVersion, args: string[]): Promise<boolean> {
|
||||
@@ -199,13 +200,13 @@ export class BsModsManagerService {
|
||||
}
|
||||
|
||||
log.info("Start download mod zip", mod.name, download.url);
|
||||
const zipPath = await this.downloadZip(download.url);
|
||||
log.info("Mod zip download end", mod.name, download.url, !!zipPath);
|
||||
if (!zipPath) {
|
||||
const zip = await this.downloadZip(download.url);
|
||||
log.info("Mod zip download end", mod.name, download.url);
|
||||
|
||||
if (!zip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const zip = await YauzlZip.fromPath(zipPath);
|
||||
let hashCount = 0;
|
||||
for await (const entry of zip.entries()) {
|
||||
const buffer = await entry.read();
|
||||
@@ -232,9 +233,6 @@ export class BsModsManagerService {
|
||||
})
|
||||
.finally(() => {
|
||||
zip.close();
|
||||
if (pathExistsSync(zipPath)) {
|
||||
unlinkSync(zipPath);
|
||||
}
|
||||
});
|
||||
|
||||
log.info("Mod zip extraction end", mod.name, "to", destDir, "success:", extracted);
|
||||
|
||||
@@ -11,3 +11,8 @@ export function findHashInString(str: string, algorithm: keyof typeof HashAlgori
|
||||
const match = regex.exec(str);
|
||||
return match ? match[0] : undefined;
|
||||
}
|
||||
|
||||
export function escapeRegExp(str: string): string {
|
||||
// Regex taken from lodash escapeRegExp function
|
||||
return str.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user