diff --git a/package-lock.json b/package-lock.json index 376b759..b723af2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "real-debrid-downloader", - "version": "1.1.27", + "version": "1.1.28", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "real-debrid-downloader", - "version": "1.1.27", + "version": "1.1.28", "license": "MIT", "dependencies": { "adm-zip": "^0.5.16", diff --git a/package.json b/package.json index 492390f..cdafa51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.1.27", + "version": "1.1.28", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "main": "build/main/main/main.js", "author": "Sucukdeluxe", diff --git a/src/main/constants.ts b/src/main/constants.ts index 2e2afe2..d9a7217 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -3,7 +3,7 @@ import os from "node:os"; import { AppSettings } from "../shared/types"; export const APP_NAME = "Debrid Download Manager"; -export const APP_VERSION = "1.1.27"; +export const APP_VERSION = "1.1.28"; export const API_BASE_URL = "https://api.real-debrid.com/rest/1.0"; export const DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload"; diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 949c106..2be2b88 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -1241,8 +1241,9 @@ export class DownloadManager extends EventEmitter { removeSamples: this.settings.removeSamplesAfterExtract }); if (result.failed > 0) { + const reason = compactErrorText(result.lastError || "Entpacken fehlgeschlagen"); for (const entry of completedItems) { - entry.fullStatus = "Entpack-Fehler"; + entry.fullStatus = `Entpack-Fehler: ${reason}`; entry.updatedAt = nowMs(); } pkg.status = "failed"; diff --git a/src/main/extractor.ts b/src/main/extractor.ts index 77593f3..c3e3060 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -6,6 +6,8 @@ import { CleanupMode, ConflictMode } from "../shared/types"; import { logger } from "./logger"; import { removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup"; +const DEFAULT_ARCHIVE_PASSWORDS = ["", "serienfans.org", "serienjunkies.org"]; + export interface ExtractOptions { packageDir: string; targetDir: string; @@ -39,39 +41,74 @@ function effectiveConflictMode(conflictMode: ConflictMode): "overwrite" | "skip" return "skip"; } -export function buildExternalExtractArgs(command: string, archivePath: string, targetDir: string, conflictMode: ConflictMode): string[] { +export function buildExternalExtractArgs( + command: string, + archivePath: string, + targetDir: string, + conflictMode: ConflictMode, + password = "" +): string[] { const mode = effectiveConflictMode(conflictMode); const lower = command.toLowerCase(); if (lower.includes("unrar")) { const overwrite = mode === "overwrite" ? "-o+" : mode === "rename" ? "-or" : "-o-"; - return ["x", overwrite, archivePath, `${targetDir}${path.sep}`]; + const pass = password ? `-p${password}` : "-p-"; + return ["x", overwrite, pass, archivePath, `${targetDir}${path.sep}`]; } const overwrite = mode === "overwrite" ? "-aoa" : mode === "rename" ? "-aou" : "-aos"; - return ["x", "-y", overwrite, archivePath, `-o${targetDir}`]; + const pass = password ? `-p${password}` : "-p"; + return ["x", "-y", overwrite, pass, archivePath, `-o${targetDir}`]; } function runExternalExtract(archivePath: string, targetDir: string, conflictMode: ConflictMode): Promise { const candidates = ["7z", "C:\\Program Files\\7-Zip\\7z.exe", "C:\\Program Files (x86)\\7-Zip\\7z.exe", "unrar"]; + const passwords = Array.from(new Set(DEFAULT_ARCHIVE_PASSWORDS)); return new Promise((resolve, reject) => { - const tryExec = (idx: number): void => { - if (idx >= candidates.length) { - reject(new Error("Kein 7z/unrar gefunden")); + let lastError = ""; + + const cleanErrorText = (text: string): string => text.replace(/\s+/g, " ").trim().slice(0, 240); + + const tryExec = (cmdIdx: number, passwordIdx: number): void => { + if (cmdIdx >= candidates.length) { + reject(new Error(lastError || "Kein 7z/unrar gefunden")); return; } - const cmd = candidates[idx]; - const args = buildExternalExtractArgs(cmd, archivePath, targetDir, conflictMode); + fs.mkdirSync(targetDir, { recursive: true }); + const cmd = candidates[cmdIdx]; + const password = passwords[passwordIdx] || ""; + const args = buildExternalExtractArgs(cmd, archivePath, targetDir, conflictMode, password); const child = spawn(cmd, args, { windowsHide: true }); - child.on("error", () => tryExec(idx + 1)); + let output = ""; + child.stdout.on("data", (chunk) => { + output += String(chunk || ""); + }); + child.stderr.on("data", (chunk) => { + output += String(chunk || ""); + }); + child.on("error", (error) => { + lastError = cleanErrorText(String(error)); + tryExec(cmdIdx + 1, 0); + }); child.on("close", (code) => { if (code === 0 || code === 1) { resolve(); } else { - tryExec(idx + 1); + const cleaned = cleanErrorText(output); + if (cleaned) { + lastError = cleaned; + } else { + lastError = `Exit Code ${String(code ?? "?")}`; + } + if (passwordIdx + 1 < passwords.length) { + tryExec(cmdIdx, passwordIdx + 1); + return; + } + tryExec(cmdIdx + 1, 0); } }); }; - tryExec(0); + tryExec(0, 0); }); } @@ -119,20 +156,25 @@ function cleanupArchives(sourceFiles: string[], cleanupMode: CleanupMode): void } } -export async function extractPackageArchives(options: ExtractOptions): Promise<{ extracted: number; failed: number }> { +export async function extractPackageArchives(options: ExtractOptions): Promise<{ extracted: number; failed: number; lastError: string }> { const candidates = findArchiveCandidates(options.packageDir); if (candidates.length === 0) { - return { extracted: 0, failed: 0 }; + return { extracted: 0, failed: 0, lastError: "" }; } let extracted = 0; let failed = 0; + let lastError = ""; const extractedArchives: string[] = []; for (const archivePath of candidates) { try { const ext = path.extname(archivePath).toLowerCase(); if (ext === ".zip") { - extractZipArchive(archivePath, options.targetDir, options.conflictMode); + try { + extractZipArchive(archivePath, options.targetDir, options.conflictMode); + } catch { + await runExternalExtract(archivePath, options.targetDir, options.conflictMode); + } } else { await runExternalExtract(archivePath, options.targetDir, options.conflictMode); } @@ -140,7 +182,9 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{ extractedArchives.push(archivePath); } catch (error) { failed += 1; - logger.error(`Entpack-Fehler ${path.basename(archivePath)}: ${String(error)}`); + const errorText = String(error); + lastError = errorText; + logger.error(`Entpack-Fehler ${path.basename(archivePath)}: ${errorText}`); } } @@ -162,5 +206,5 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{ } } - return { extracted, failed }; + return { extracted, failed, lastError }; } diff --git a/tests/extractor.test.ts b/tests/extractor.test.ts index 08e4bda..3b69b58 100644 --- a/tests/extractor.test.ts +++ b/tests/extractor.test.ts @@ -19,6 +19,7 @@ describe("extractor", () => { "x", "-y", "-aoa", + "-p", "archive.rar", "-oC:\\target" ]); @@ -26,6 +27,15 @@ describe("extractor", () => { "x", "-y", "-aos", + "-p", + "archive.rar", + "-oC:\\target" + ]); + expect(buildExternalExtractArgs("7z", "archive.rar", "C:\\target", "ask", "serienfans.org")).toEqual([ + "x", + "-y", + "-aos", + "-pserienfans.org", "archive.rar", "-oC:\\target" ]); @@ -33,7 +43,8 @@ describe("extractor", () => { const unrarRename = buildExternalExtractArgs("unrar", "archive.rar", "C:\\target", "rename"); expect(unrarRename[0]).toBe("x"); expect(unrarRename[1]).toBe("-or"); - expect(unrarRename[2]).toBe("archive.rar"); + expect(unrarRename[2]).toBe("-p-"); + expect(unrarRename[3]).toBe("archive.rar"); }); it("deletes only successfully extracted archives", async () => {