mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-19 01:03:57 +00:00
208 lines
5.4 KiB
TypeScript
208 lines
5.4 KiB
TypeScript
import path from "node:path";
|
|
import fs from "node:fs";
|
|
import type { GameShop } from "@types";
|
|
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
|
import { FILE_EXTENSIONS_TO_EXTRACT } from "@shared";
|
|
import { SevenZip, ExtractionProgress } from "./7zip";
|
|
import { WindowManager } from "./window-manager";
|
|
import { publishExtractionCompleteNotification } from "./notifications";
|
|
import { logger } from "./logger";
|
|
|
|
const PROGRESS_THROTTLE_MS = 1000;
|
|
|
|
export class GameFilesManager {
|
|
private lastProgressUpdate = 0;
|
|
|
|
constructor(
|
|
private readonly shop: GameShop,
|
|
private readonly objectId: string
|
|
) {}
|
|
|
|
private get gameKey() {
|
|
return levelKeys.game(this.shop, this.objectId);
|
|
}
|
|
|
|
private async updateExtractionProgress(progress: number, force = false) {
|
|
const now = Date.now();
|
|
|
|
if (!force && now - this.lastProgressUpdate < PROGRESS_THROTTLE_MS) {
|
|
return;
|
|
}
|
|
|
|
this.lastProgressUpdate = now;
|
|
|
|
const download = await downloadsSublevel.get(this.gameKey);
|
|
if (!download) return;
|
|
|
|
await downloadsSublevel.put(this.gameKey, {
|
|
...download,
|
|
extractionProgress: progress,
|
|
});
|
|
|
|
WindowManager.mainWindow?.webContents.send(
|
|
"on-extraction-progress",
|
|
this.shop,
|
|
this.objectId,
|
|
progress
|
|
);
|
|
}
|
|
|
|
private async clearExtractionState() {
|
|
const download = await downloadsSublevel.get(this.gameKey);
|
|
if (!download) return;
|
|
|
|
await downloadsSublevel.put(this.gameKey, {
|
|
...download,
|
|
extracting: false,
|
|
extractionProgress: 0,
|
|
});
|
|
|
|
WindowManager.mainWindow?.webContents.send(
|
|
"on-extraction-complete",
|
|
this.shop,
|
|
this.objectId
|
|
);
|
|
}
|
|
|
|
private readonly handleProgress = (progress: ExtractionProgress) => {
|
|
this.updateExtractionProgress(progress.percent / 100);
|
|
};
|
|
|
|
async extractFilesInDirectory(directoryPath: string) {
|
|
if (!fs.existsSync(directoryPath)) return;
|
|
const files = await fs.promises.readdir(directoryPath);
|
|
|
|
const compressedFiles = files.filter((file) =>
|
|
FILE_EXTENSIONS_TO_EXTRACT.some((ext) => file.endsWith(ext))
|
|
);
|
|
|
|
const filesToExtract = compressedFiles.filter(
|
|
(file) => /part1\.rar$/i.test(file) || !/part\d+\.rar$/i.test(file)
|
|
);
|
|
|
|
if (filesToExtract.length === 0) return;
|
|
|
|
await this.updateExtractionProgress(0, true);
|
|
|
|
const totalFiles = filesToExtract.length;
|
|
let completedFiles = 0;
|
|
|
|
for (const file of filesToExtract) {
|
|
try {
|
|
const result = await SevenZip.extractFile(
|
|
{
|
|
filePath: path.join(directoryPath, file),
|
|
cwd: directoryPath,
|
|
passwords: ["online-fix.me", "steamrip.com"],
|
|
},
|
|
(progress) => {
|
|
const overallProgress =
|
|
(completedFiles + progress.percent / 100) / totalFiles;
|
|
this.updateExtractionProgress(overallProgress);
|
|
}
|
|
);
|
|
|
|
if (result.success) {
|
|
completedFiles++;
|
|
await this.updateExtractionProgress(
|
|
completedFiles / totalFiles,
|
|
true
|
|
);
|
|
}
|
|
} catch (err) {
|
|
logger.error(`Failed to extract file: ${file}`, err);
|
|
await this.clearExtractionState();
|
|
return;
|
|
}
|
|
}
|
|
|
|
const archivePaths = compressedFiles
|
|
.map((file) => path.join(directoryPath, file))
|
|
.filter((archivePath) => fs.existsSync(archivePath));
|
|
|
|
if (archivePaths.length > 0) {
|
|
WindowManager.mainWindow?.webContents.send(
|
|
"on-archive-deletion-prompt",
|
|
archivePaths
|
|
);
|
|
}
|
|
}
|
|
|
|
async setExtractionComplete(publishNotification = true) {
|
|
const [download, game] = await Promise.all([
|
|
downloadsSublevel.get(this.gameKey),
|
|
gamesSublevel.get(this.gameKey),
|
|
]);
|
|
|
|
if (!download) return;
|
|
|
|
await downloadsSublevel.put(this.gameKey, {
|
|
...download,
|
|
extracting: false,
|
|
extractionProgress: 0,
|
|
});
|
|
|
|
WindowManager.mainWindow?.webContents.send(
|
|
"on-extraction-complete",
|
|
this.shop,
|
|
this.objectId
|
|
);
|
|
|
|
if (publishNotification && game) {
|
|
publishExtractionCompleteNotification(game);
|
|
}
|
|
}
|
|
|
|
async extractDownloadedFile() {
|
|
const [download, game] = await Promise.all([
|
|
downloadsSublevel.get(this.gameKey),
|
|
gamesSublevel.get(this.gameKey),
|
|
]);
|
|
|
|
if (!download || !game) return false;
|
|
|
|
const filePath = path.join(download.downloadPath, download.folderName!);
|
|
|
|
const extractionPath = path.join(
|
|
download.downloadPath,
|
|
path.parse(download.folderName!).name
|
|
);
|
|
|
|
await this.updateExtractionProgress(0, true);
|
|
|
|
try {
|
|
const result = await SevenZip.extractFile(
|
|
{
|
|
filePath,
|
|
outputPath: extractionPath,
|
|
passwords: ["online-fix.me", "steamrip.com"],
|
|
},
|
|
this.handleProgress
|
|
);
|
|
|
|
if (result.success) {
|
|
await this.extractFilesInDirectory(extractionPath);
|
|
|
|
if (fs.existsSync(extractionPath) && fs.existsSync(filePath)) {
|
|
WindowManager.mainWindow?.webContents.send(
|
|
"on-archive-deletion-prompt",
|
|
[filePath]
|
|
);
|
|
}
|
|
|
|
await downloadsSublevel.put(this.gameKey, {
|
|
...download,
|
|
folderName: path.parse(download.folderName!).name,
|
|
});
|
|
|
|
await this.setExtractionComplete();
|
|
}
|
|
} catch (err) {
|
|
logger.error(`Failed to extract downloaded file: ${filePath}`, err);
|
|
await this.clearExtractionState();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|