Skip to content

Commit

Permalink
refactor: extractVvpp周りのTODOを解決 (#2499)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hiroshiba authored Jan 22, 2025
1 parent bd4cfbd commit 3c69896
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 43 deletions.
9 changes: 6 additions & 3 deletions src/backend/electron/manager/vvppManager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { moveFile } from "move-file";
import { dialog } from "electron";
import { app, dialog } from "electron";
import AsyncLock from "async-lock";
import {
EngineId,
Expand Down Expand Up @@ -114,8 +114,11 @@ export class VvppManager {
callbacks?: { onProgress?: ProgressCallback },
) {
const { outputDir, manifest } = await extractVvpp(
vvppPath,
this.vvppEngineDir,
{
vvppLikeFilePath: vvppPath,
vvppEngineDir: this.vvppEngineDir,
tmpDir: app.getPath("temp"),
},
callbacks,
);

Expand Down
65 changes: 34 additions & 31 deletions src/backend/electron/vvppFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,6 @@ import { createLogger } from "@/helpers/log";

const log = createLogger("vvppFile");

// https://www.garykessler.net/library/file_sigs.html#:~:text=7-zip%20compressed%20file
const SEVEN_ZIP_MAGIC_NUMBER = Buffer.from([
0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c,
]);

const ZIP_MAGIC_NUMBER = Buffer.from([0x50, 0x4b, 0x03, 0x04]);

/** VVPPファイルが分割されている場合、それらのファイルを取得する */
async function getArchiveFileParts(
vvppLikeFilePath: string,
Expand Down Expand Up @@ -61,21 +54,14 @@ async function getArchiveFileParts(

/** 分割されているVVPPファイルを連結して返す */
async function concatenateVvppFiles(
format: "zip" | "7z",
archiveFileParts: string[],
outputFilePath: string,
) {
// -siオプションでの7z解凍はサポートされていないため、
// ファイルを連結した一次ファイルを作成し、それを7zで解凍する。
log.info(`Concatenating ${archiveFileParts.length} files...`);
const tmpConcatenatedFile = path.join(
app.getPath("temp"), // TODO: archiveFilePartsと同じディレクトリにしてappの依存をなくす
`vvpp-${new Date().getTime()}.${format}`,
);
log.info("Temporary file:", tmpConcatenatedFile);

await new Promise<void>((resolve, reject) => {
if (!tmpConcatenatedFile) throw new Error("tmpFile is undefined");
const inputStreams = archiveFileParts.map((f) => fs.createReadStream(f));
const outputStream = fs.createWriteStream(tmpConcatenatedFile);
const outputStream = fs.createWriteStream(outputFilePath);
new MultiStream(inputStreams)
.pipe(outputStream)
.on("close", () => {
Expand All @@ -85,7 +71,6 @@ async function concatenateVvppFiles(
.on("error", reject);
});
log.info("Concatenated");
return tmpConcatenatedFile;
}

/** 7zでファイルを解凍する */
Expand All @@ -107,13 +92,7 @@ async function unarchive(
"-bsp1", // 進捗出力
];

let sevenZipPath = import.meta.env.VITE_7Z_BIN_NAME;
if (!sevenZipPath) {
throw new Error("7z path is not defined");
}
if (import.meta.env.PROD) {
sevenZipPath = path.join(path.dirname(app.getPath("exe")), sevenZipPath); // TODO: helperに移動してappの依存をなくす
}
const sevenZipPath = getSevenZipPath();
log.info("Spawning 7z:", sevenZipPath, args.join(" "));
await new Promise<void>((resolve, reject) => {
const child = spawn(sevenZipPath, args, {
Expand Down Expand Up @@ -152,13 +131,24 @@ async function unarchive(
// FIXME: rejectが2回呼ばれることがある
child.on("error", reject);
});

function getSevenZipPath() {
let sevenZipPath = import.meta.env.VITE_7Z_BIN_NAME;
if (!sevenZipPath) {
throw new Error("7z path is not defined");
}
if (import.meta.env.PROD) {
sevenZipPath = path.join(path.dirname(app.getPath("exe")), sevenZipPath);
}
return sevenZipPath;
}
}

export async function extractVvpp(
vvppLikeFilePath: string,
vvppEngineDir: string, // TODO: payload objectに変える
payload: { vvppLikeFilePath: string; vvppEngineDir: string; tmpDir: string },
callbacks?: { onProgress?: ProgressCallback },
): Promise<{ outputDir: string; manifest: MinimumEngineManifestType }> {
const { vvppLikeFilePath, vvppEngineDir, tmpDir } = payload;
callbacks?.onProgress?.({ progress: 0 });

const nonce = new Date().getTime().toString();
Expand All @@ -177,10 +167,12 @@ export async function extractVvpp(
let archiveFile: string;
try {
if (archiveFileParts.length > 1) {
tmpConcatenatedFile = await concatenateVvppFiles(
format,
archiveFileParts,
);
// -siオプションでの7z解凍はサポートされていないため、
// ファイルを連結した一次ファイルを作成し、それを7zで解凍する。
tmpConcatenatedFile = createTmpConcatenatedFilePath();
log.info("Temporary file:", tmpConcatenatedFile);

await concatenateVvppFiles(archiveFileParts, tmpConcatenatedFile);
archiveFile = tmpConcatenatedFile;
} else {
archiveFile = archiveFileParts[0];
Expand Down Expand Up @@ -214,6 +206,10 @@ export async function extractVvpp(
}
throw e;
}

function createTmpConcatenatedFilePath(): string {
return path.join(tmpDir, `vvpp-${new Date().getTime()}.${format}`);
}
}

async function detectFileFormat(
Expand All @@ -225,6 +221,13 @@ async function detectFileFormat(
await file.read(buffer, 0, 8, 0);
await file.close();

// https://www.garykessler.net/library/file_sigs.html#:~:text=7-zip%20compressed%20file
const SEVEN_ZIP_MAGIC_NUMBER = Buffer.from([
0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c,
]);

const ZIP_MAGIC_NUMBER = Buffer.from([0x50, 0x4b, 0x03, 0x04]);

if (buffer.compare(SEVEN_ZIP_MAGIC_NUMBER, 0, 6, 0, 6) === 0) {
return "7z";
} else if (buffer.compare(ZIP_MAGIC_NUMBER, 0, 4, 0, 4) === 0) {
Expand Down
50 changes: 41 additions & 9 deletions tests/unit/backend/electron/vvppFile.node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ test("正しいVVPPファイルからエンジンを切り出せる", async () =
const sourceDir = path.join(__dirname, "vvpps", targetName);
const outputFilePath = path.join(tmpDir, uuid4() + targetName);
await createZipFile(sourceDir, outputFilePath);
await extractVvpp(outputFilePath, tmpDir);

const vvppEngineDir = createVvppEngineDir();
await extractVvpp({
vvppLikeFilePath: outputFilePath,
vvppEngineDir,
tmpDir,
});
expectManifestExists(vvppEngineDir);
});

test.fails("分割されたVVPPファイルからエンジンを切り出せる", async () => {
// TODO: electronのappに依存しているのでテストが通らない。依存がなくなり次第.failsを失くす
test("分割されたVVPPファイルからエンジンを切り出せる", async () => {
const targetName = "perfect.vvpp";
const sourceDir = path.join(__dirname, "vvpps", targetName);
const outputFilePath = path.join(tmpDir, uuid4() + targetName);
Expand All @@ -33,7 +39,14 @@ test.fails("分割されたVVPPファイルからエンジンを切り出せる"
const outputFilePath1 = outputFilePath + ".1.vvppp";
const outputFilePath2 = outputFilePath + ".2.vvppp";
splitFile(outputFilePath, outputFilePath1, outputFilePath2);
await extractVvpp(outputFilePath1, tmpDir);

const vvppEngineDir = createVvppEngineDir();
await extractVvpp({
vvppLikeFilePath: outputFilePath1,
vvppEngineDir,
tmpDir,
});
expectManifestExists(vvppEngineDir);
});

test.each([
Expand All @@ -46,19 +59,38 @@ test.each([
const sourceDir = path.join(__dirname, "vvpps", targetName);
const outputFilePath = path.join(tmpDir, uuid4() + targetName);
await createZipFile(sourceDir, outputFilePath);
await expect(extractVvpp(outputFilePath, tmpDir)).rejects.toThrow(
expectedError,
);
await expect(
extractVvpp({
vvppLikeFilePath: outputFilePath,
vvppEngineDir: tmpDir,
tmpDir,
}),
).rejects.toThrow(expectedError);
},
);

/** 7zを使って指定したフォルダからzipファイルを作成する */
async function createZipFile(sourceDir: string, outputFilePath: string) {
const zipBin = import.meta.env.VITE_7Z_BIN_NAME;
const command = `"${zipBin}" a -tzip "${outputFilePath}" "${sourceDir}\\*"`;
const sevenZipBin = import.meta.env.VITE_7Z_BIN_NAME;
const command = `"${sevenZipBin}" a -tzip "${outputFilePath}" "${path.join(sourceDir, "*")}"`;
await promisify(exec)(command);
}

function createVvppEngineDir() {
const dir = path.join(tmpDir, uuid4());
fs.mkdirSync(dir);
return dir;
}

function expectManifestExists(vvppEngineDir: string) {
const files = fs.readdirSync(vvppEngineDir, { recursive: true });
const manifestExists = files.some(
(file) =>
typeof file === "string" && path.basename(file) == "engine_manifest.json",
);
expect(manifestExists).toBe(true);
}

/** ファイルを2つに分割する */
function splitFile(
inputFilePath: string,
Expand Down

0 comments on commit 3c69896

Please sign in to comment.