real-debrid-downloader/src/main/extractor.ts
Sucukdeluxe b96ed1eb7a
Some checks are pending
Build and Release / build (push) Waiting to run
Migrate app to Node Electron with modern React UI
2026-02-27 03:25:56 +01:00

138 lines
4.3 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { spawn } from "node:child_process";
import AdmZip from "adm-zip";
import { CleanupMode, ConflictMode } from "../shared/types";
import { logger } from "./logger";
import { removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup";
export interface ExtractOptions {
packageDir: string;
targetDir: string;
cleanupMode: CleanupMode;
conflictMode: ConflictMode;
removeLinks: boolean;
removeSamples: boolean;
}
function findArchiveCandidates(packageDir: string): string[] {
const files = fs.readdirSync(packageDir, { withFileTypes: true })
.filter((entry) => entry.isFile())
.map((entry) => path.join(packageDir, entry.name));
const preferred = files.filter((file) => /\.part0*1\.rar$/i.test(file));
const zip = files.filter((file) => /\.zip$/i.test(file));
const singleRar = files.filter((file) => /\.rar$/i.test(file) && !/\.part\d+\.rar$/i.test(file));
const seven = files.filter((file) => /\.7z$/i.test(file));
const ordered = [...preferred, ...zip, ...singleRar, ...seven];
return Array.from(new Set(ordered));
}
function runExternalExtract(archivePath: string, targetDir: string): Promise<void> {
const candidates = ["7z", "C:\\Program Files\\7-Zip\\7z.exe", "C:\\Program Files (x86)\\7-Zip\\7z.exe", "unrar"];
return new Promise((resolve, reject) => {
const tryExec = (idx: number): void => {
if (idx >= candidates.length) {
reject(new Error("Kein 7z/unrar gefunden"));
return;
}
const cmd = candidates[idx];
const args = cmd.toLowerCase().includes("unrar")
? ["x", "-o+", archivePath, `${targetDir}${path.sep}`]
: ["x", "-y", archivePath, `-o${targetDir}`];
const child = spawn(cmd, args, { windowsHide: true });
child.on("error", () => tryExec(idx + 1));
child.on("close", (code) => {
if (code === 0 || code === 1) {
resolve();
} else {
tryExec(idx + 1);
}
});
};
tryExec(0);
});
}
function extractZipArchive(archivePath: string, targetDir: string, conflictMode: ConflictMode): void {
const zip = new AdmZip(archivePath);
const entries = zip.getEntries();
for (const entry of entries) {
const outputPath = path.join(targetDir, entry.entryName);
if (entry.isDirectory) {
fs.mkdirSync(outputPath, { recursive: true });
continue;
}
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
if (fs.existsSync(outputPath)) {
if (conflictMode === "skip") {
continue;
}
if (conflictMode === "rename") {
const parsed = path.parse(outputPath);
let n = 1;
let candidate = outputPath;
while (fs.existsSync(candidate)) {
candidate = path.join(parsed.dir, `${parsed.name} (${n})${parsed.ext}`);
n += 1;
}
fs.writeFileSync(candidate, entry.getData());
continue;
}
}
fs.writeFileSync(outputPath, entry.getData());
}
}
function cleanupArchives(sourceFiles: string[], cleanupMode: CleanupMode): void {
if (cleanupMode === "none") {
return;
}
for (const filePath of sourceFiles) {
try {
fs.rmSync(filePath, { force: true });
} catch {
// ignore
}
}
}
export async function extractPackageArchives(options: ExtractOptions): Promise<{ extracted: number; failed: number }> {
const candidates = findArchiveCandidates(options.packageDir);
if (candidates.length === 0) {
return { extracted: 0, failed: 0 };
}
fs.mkdirSync(options.targetDir, { recursive: true });
let extracted = 0;
let failed = 0;
for (const archivePath of candidates) {
try {
const ext = path.extname(archivePath).toLowerCase();
if (ext === ".zip") {
extractZipArchive(archivePath, options.targetDir, options.conflictMode);
} else {
await runExternalExtract(archivePath, options.targetDir);
}
extracted += 1;
} catch (error) {
failed += 1;
logger.error(`Entpack-Fehler ${path.basename(archivePath)}: ${String(error)}`);
}
}
if (extracted > 0) {
cleanupArchives(candidates, options.cleanupMode);
if (options.removeLinks) {
removeDownloadLinkArtifacts(options.targetDir);
}
if (options.removeSamples) {
removeSampleArtifacts(options.targetDir);
}
}
return { extracted, failed };
}