diff --git a/src/main/extractor.ts b/src/main/extractor.ts index ff9f492..f0b73fa 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -694,11 +694,14 @@ function nativeExtractorCandidates(archivePath = ""): string[] { } const winRarInstalled = [ + path.join(programFiles, "WinRAR", "Rar.exe"), + path.join(programFilesX86, "WinRAR", "Rar.exe"), path.join(programFiles, "WinRAR", "UnRAR.exe"), path.join(programFilesX86, "WinRAR", "UnRAR.exe") ]; if (localAppData) { + winRarInstalled.push(path.join(localAppData, "Programs", "WinRAR", "Rar.exe")); winRarInstalled.push(path.join(localAppData, "Programs", "WinRAR", "UnRAR.exe")); } @@ -711,6 +714,8 @@ function nativeExtractorCandidates(archivePath = ""): string[] { "7za.exe", "7za", ...winRarInstalled, + "Rar.exe", + "rar", "UnRAR.exe", "unrar" ] @@ -721,6 +726,8 @@ function nativeExtractorCandidates(archivePath = ""): string[] { "7za.exe", "7za", ...winRarInstalled, + "Rar.exe", + "rar", "UnRAR.exe", "unrar" ]; @@ -1763,8 +1770,7 @@ export function buildExternalExtractArgs( flatMode = false ): string[] { const mode = effectiveConflictMode(conflictMode); - const lower = command.toLowerCase(); - if (lower.includes("unrar") || lower.includes("winrar")) { + if (isRarNativeCommand(command)) { // "e" extracts without paths (flat). Used as fallback when the archive stores paths with a // leading \ (absolute-style), which causes UnRAR to produce invalid \\ double-separators. const extractCmd = flatMode ? "e" : "x"; @@ -1804,8 +1810,7 @@ async function resolveExtractorCommandInternal(archivePath = ""): Promise entry.command); } +function extractorProbeArgs(command: string): string[] { + return isRarNativeCommand(command) ? ["-?"] : ["?"]; +} + async function findAlternativeExtractor(currentCommand: string, archivePath = ""): Promise { const candidates = nativeExtractorCandidates(archivePath); - const currentIs7z = is7zCommand(currentCommand); - for (const candidate of candidates) { - if (candidate === currentCommand) continue; - // If current is 7z, look for UnRAR/WinRAR. If current is UnRAR, look for 7z. - if (currentIs7z && !isUnrarCommand(candidate)) continue; - if (!currentIs7z && !is7zCommand(candidate)) continue; - if (isAbsoluteCommand(candidate) && !fs.existsSync(candidate)) continue; - const lower = candidate.toLowerCase(); - const probeArgs = (lower.includes("winrar") || lower.includes("unrar")) ? ["-?"] : ["?"]; - const probe = await runExtractCommand(candidate, probeArgs, undefined, undefined, EXTRACTOR_PROBE_TIMEOUT_MS); - if (probe.ok || (!probe.missingCommand && !probe.timedOut)) { - return candidate; + const currentKind = extractorCommandKind(currentCommand); + const preferredKinds: ExtractorCommandKind[] = currentKind === "seven_zip" + ? ["rar_native"] + : isRarArchivePath(archivePath) + ? ["rar_native", "seven_zip"] + : ["seven_zip", "rar_native"]; + for (const kind of preferredKinds) { + for (const candidate of candidates) { + if (candidate === currentCommand) continue; + if (extractorCommandKind(candidate) !== kind) continue; + if (isAbsoluteCommand(candidate) && !fs.existsSync(candidate)) continue; + const probe = await runExtractCommand(candidate, extractorProbeArgs(candidate), undefined, undefined, EXTRACTOR_PROBE_TIMEOUT_MS); + if (probe.ok || (!probe.missingCommand && !probe.timedOut)) { + return candidate; + } } } return null; @@ -2194,6 +2210,7 @@ async function runExternalExtractInner( let bestPercent = 0; let passwordAttempt = 0; let usePerformanceFlags = externalExtractorSupportsPerfFlags && shouldUseExtractorPerformanceFlags(); + const summarizeResultError = (errorText: string): string => cleanErrorText(errorText).slice(0, 280); // Skip normal extraction loop if flat mode is already known to be needed for this package if (forceFlatMode) { @@ -2270,11 +2287,18 @@ async function runExternalExtractInner( }, signal, timeoutMs); } - logger.info( - `Legacy-Passwort-Versuch Ergebnis: archive=${path.basename(archivePath)}, attempt=${passwordAttempt}/${passwords.length}, ` + - `ms=${Date.now() - attemptStartedAt}, ok=${result.ok}, timedOut=${result.timedOut}, missingCommand=${result.missingCommand}, bestPercent=${bestPercent}` - ); - onLog?.("INFO", `Legacy-Passwort-Versuch Ergebnis: archive=${path.basename(archivePath)}, attempt=${passwordAttempt}/${passwords.length}, ms=${Date.now() - attemptStartedAt}, ok=${result.ok}, timedOut=${result.timedOut}, missingCommand=${result.missingCommand}, bestPercent=${bestPercent}`); + logger.info( + `Legacy-Passwort-Versuch Ergebnis: archive=${path.basename(archivePath)}, attempt=${passwordAttempt}/${passwords.length}, ` + + `ms=${Date.now() - attemptStartedAt}, ok=${result.ok}, timedOut=${result.timedOut}, missingCommand=${result.missingCommand}, bestPercent=${bestPercent}` + ); + onLog?.("INFO", `Legacy-Passwort-Versuch Ergebnis: archive=${path.basename(archivePath)}, attempt=${passwordAttempt}/${passwords.length}, ms=${Date.now() - attemptStartedAt}, ok=${result.ok}, timedOut=${result.timedOut}, missingCommand=${result.missingCommand}, bestPercent=${bestPercent}`); + if (!result.ok) { + const errorSummary = summarizeResultError(result.errorText); + if (errorSummary) { + logger.info(`Legacy-Passwort-Versuch Fehlertext: archive=${path.basename(archivePath)}, attempt=${passwordAttempt}/${passwords.length}, extractor=${extractorName}, error=${errorSummary}`); + onLog?.("INFO", `Legacy-Passwort-Versuch Fehlertext: archive=${path.basename(archivePath)}, attempt=${passwordAttempt}/${passwords.length}, extractor=${extractorName}, error=${errorSummary}`); + } + } if (result.ok) { onArchiveProgress?.(100); diff --git a/tests/extractor.test.ts b/tests/extractor.test.ts index 889c315..d4863fe 100644 --- a/tests/extractor.test.ts +++ b/tests/extractor.test.ts @@ -68,6 +68,11 @@ describe("extractor", () => { expect(unrarRename[2]).toBe("-p-"); expect(unrarRename[3]).toBe("-y"); expect(unrarRename[unrarRename.length - 2]).toBe("archive.rar"); + + const rarCliArgs = buildExternalExtractArgs("Rar.exe", "archive.rar", "C:\\target", "overwrite", "serienjunkies.org"); + expect(rarCliArgs.slice(0, 4)).toEqual(["x", "-o+", "-pserienjunkies.org", "-y"]); + expect(rarCliArgs[rarCliArgs.length - 2]).toBe("archive.rar"); + expect(rarCliArgs[rarCliArgs.length - 1]).toBe("C:\\target\\"); }); it("deletes only successfully extracted archives", async () => { @@ -1174,13 +1179,13 @@ describe("extractor", () => { }); describe("orderExtractorCandidatesForArchive", () => { - it("prefers WinRAR/UnRAR over 7-Zip for rar archives", () => { + it("prefers RAR-native CLIs over 7-Zip for rar archives", () => { const ordered = orderExtractorCandidatesForArchive( - ["7z.exe", "UnRAR.exe", "WinRAR.exe"], + ["7z.exe", "Rar.exe", "UnRAR.exe", "WinRAR.exe"], "C:\\Downloads\\archive.part01.rar" ); - expect(ordered.slice(0, 2)).toEqual(["UnRAR.exe", "WinRAR.exe"]); - expect(ordered[2]).toBe("7z.exe"); + expect(ordered.slice(0, 3)).toEqual(["Rar.exe", "UnRAR.exe", "WinRAR.exe"]); + expect(ordered[3]).toBe("7z.exe"); }); it("keeps 7-Zip first for non-rar archives", () => {