Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: extractVvpp周りのTODOを解決 #2499

Merged
merged 9 commits into from
Jan 22, 2025
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"),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vvpppを結合したものができるファイル、やっぱりelectronの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]);
Comment on lines +224 to +229
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

スコープは短いほど良いので関数内に移動・・・
というよりこれは単純に場所が近いほうがわかりやすいからというのが大きそう。


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
Loading