Fix RAR native extractor fallback
This commit is contained in:
parent
53c411f635
commit
935f05e214
@ -694,11 +694,14 @@ function nativeExtractorCandidates(archivePath = ""): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const winRarInstalled = [
|
const winRarInstalled = [
|
||||||
|
path.join(programFiles, "WinRAR", "Rar.exe"),
|
||||||
|
path.join(programFilesX86, "WinRAR", "Rar.exe"),
|
||||||
path.join(programFiles, "WinRAR", "UnRAR.exe"),
|
path.join(programFiles, "WinRAR", "UnRAR.exe"),
|
||||||
path.join(programFilesX86, "WinRAR", "UnRAR.exe")
|
path.join(programFilesX86, "WinRAR", "UnRAR.exe")
|
||||||
];
|
];
|
||||||
|
|
||||||
if (localAppData) {
|
if (localAppData) {
|
||||||
|
winRarInstalled.push(path.join(localAppData, "Programs", "WinRAR", "Rar.exe"));
|
||||||
winRarInstalled.push(path.join(localAppData, "Programs", "WinRAR", "UnRAR.exe"));
|
winRarInstalled.push(path.join(localAppData, "Programs", "WinRAR", "UnRAR.exe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -711,6 +714,8 @@ function nativeExtractorCandidates(archivePath = ""): string[] {
|
|||||||
"7za.exe",
|
"7za.exe",
|
||||||
"7za",
|
"7za",
|
||||||
...winRarInstalled,
|
...winRarInstalled,
|
||||||
|
"Rar.exe",
|
||||||
|
"rar",
|
||||||
"UnRAR.exe",
|
"UnRAR.exe",
|
||||||
"unrar"
|
"unrar"
|
||||||
]
|
]
|
||||||
@ -721,6 +726,8 @@ function nativeExtractorCandidates(archivePath = ""): string[] {
|
|||||||
"7za.exe",
|
"7za.exe",
|
||||||
"7za",
|
"7za",
|
||||||
...winRarInstalled,
|
...winRarInstalled,
|
||||||
|
"Rar.exe",
|
||||||
|
"rar",
|
||||||
"UnRAR.exe",
|
"UnRAR.exe",
|
||||||
"unrar"
|
"unrar"
|
||||||
];
|
];
|
||||||
@ -1763,8 +1770,7 @@ export function buildExternalExtractArgs(
|
|||||||
flatMode = false
|
flatMode = false
|
||||||
): string[] {
|
): string[] {
|
||||||
const mode = effectiveConflictMode(conflictMode);
|
const mode = effectiveConflictMode(conflictMode);
|
||||||
const lower = command.toLowerCase();
|
if (isRarNativeCommand(command)) {
|
||||||
if (lower.includes("unrar") || lower.includes("winrar")) {
|
|
||||||
// "e" extracts without paths (flat). Used as fallback when the archive stores paths with a
|
// "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.
|
// leading \ (absolute-style), which causes UnRAR to produce invalid \\ double-separators.
|
||||||
const extractCmd = flatMode ? "e" : "x";
|
const extractCmd = flatMode ? "e" : "x";
|
||||||
@ -1804,8 +1810,7 @@ async function resolveExtractorCommandInternal(archivePath = ""): Promise<string
|
|||||||
if (isAbsoluteCommand(command) && !fs.existsSync(command)) {
|
if (isAbsoluteCommand(command) && !fs.existsSync(command)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const lower = command.toLowerCase();
|
const probeArgs = extractorProbeArgs(command);
|
||||||
const probeArgs = (lower.includes("winrar") || lower.includes("unrar")) ? ["-?"] : ["?"];
|
|
||||||
const probe = await runExtractCommand(command, probeArgs, undefined, undefined, EXTRACTOR_PROBE_TIMEOUT_MS);
|
const probe = await runExtractCommand(command, probeArgs, undefined, undefined, EXTRACTOR_PROBE_TIMEOUT_MS);
|
||||||
if (probe.ok || (!probe.missingCommand && !probe.timedOut)) {
|
if (probe.ok || (!probe.missingCommand && !probe.timedOut)) {
|
||||||
resolvedExtractorCommand = command;
|
resolvedExtractorCommand = command;
|
||||||
@ -1845,15 +1850,19 @@ function is7zCommand(command: string): boolean {
|
|||||||
return lower.includes("7z") && !lower.includes("unrar") && !lower.includes("winrar");
|
return lower.includes("7z") && !lower.includes("unrar") && !lower.includes("winrar");
|
||||||
}
|
}
|
||||||
|
|
||||||
function isUnrarCommand(command: string): boolean {
|
function isRarNativeCommand(command: string): boolean {
|
||||||
const lower = command.toLowerCase();
|
const base = path.basename(String(command || "")).toLowerCase();
|
||||||
return lower.includes("unrar") || lower.includes("winrar");
|
return base === "unrar.exe"
|
||||||
|
|| base === "unrar"
|
||||||
|
|| base === "winrar.exe"
|
||||||
|
|| base === "rar.exe"
|
||||||
|
|| base === "rar";
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtractorCommandKind = "rar_native" | "seven_zip" | "other";
|
type ExtractorCommandKind = "rar_native" | "seven_zip" | "other";
|
||||||
|
|
||||||
function extractorCommandKind(command: string): ExtractorCommandKind {
|
function extractorCommandKind(command: string): ExtractorCommandKind {
|
||||||
if (isUnrarCommand(command)) {
|
if (isRarNativeCommand(command)) {
|
||||||
return "rar_native";
|
return "rar_native";
|
||||||
}
|
}
|
||||||
if (is7zCommand(command)) {
|
if (is7zCommand(command)) {
|
||||||
@ -1913,22 +1922,29 @@ export function orderExtractorCandidatesForArchive(
|
|||||||
.map((entry) => entry.command);
|
.map((entry) => entry.command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractorProbeArgs(command: string): string[] {
|
||||||
|
return isRarNativeCommand(command) ? ["-?"] : ["?"];
|
||||||
|
}
|
||||||
|
|
||||||
async function findAlternativeExtractor(currentCommand: string, archivePath = ""): Promise<string | null> {
|
async function findAlternativeExtractor(currentCommand: string, archivePath = ""): Promise<string | null> {
|
||||||
const candidates = nativeExtractorCandidates(archivePath);
|
const candidates = nativeExtractorCandidates(archivePath);
|
||||||
const currentIs7z = is7zCommand(currentCommand);
|
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) {
|
for (const candidate of candidates) {
|
||||||
if (candidate === currentCommand) continue;
|
if (candidate === currentCommand) continue;
|
||||||
// If current is 7z, look for UnRAR/WinRAR. If current is UnRAR, look for 7z.
|
if (extractorCommandKind(candidate) !== kind) continue;
|
||||||
if (currentIs7z && !isUnrarCommand(candidate)) continue;
|
|
||||||
if (!currentIs7z && !is7zCommand(candidate)) continue;
|
|
||||||
if (isAbsoluteCommand(candidate) && !fs.existsSync(candidate)) continue;
|
if (isAbsoluteCommand(candidate) && !fs.existsSync(candidate)) continue;
|
||||||
const lower = candidate.toLowerCase();
|
const probe = await runExtractCommand(candidate, extractorProbeArgs(candidate), undefined, undefined, EXTRACTOR_PROBE_TIMEOUT_MS);
|
||||||
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)) {
|
if (probe.ok || (!probe.missingCommand && !probe.timedOut)) {
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2194,6 +2210,7 @@ async function runExternalExtractInner(
|
|||||||
let bestPercent = 0;
|
let bestPercent = 0;
|
||||||
let passwordAttempt = 0;
|
let passwordAttempt = 0;
|
||||||
let usePerformanceFlags = externalExtractorSupportsPerfFlags && shouldUseExtractorPerformanceFlags();
|
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
|
// Skip normal extraction loop if flat mode is already known to be needed for this package
|
||||||
if (forceFlatMode) {
|
if (forceFlatMode) {
|
||||||
@ -2275,6 +2292,13 @@ async function runExternalExtractInner(
|
|||||||
`ms=${Date.now() - attemptStartedAt}, ok=${result.ok}, timedOut=${result.timedOut}, missingCommand=${result.missingCommand}, bestPercent=${bestPercent}`
|
`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}`);
|
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) {
|
if (result.ok) {
|
||||||
onArchiveProgress?.(100);
|
onArchiveProgress?.(100);
|
||||||
|
|||||||
@ -68,6 +68,11 @@ describe("extractor", () => {
|
|||||||
expect(unrarRename[2]).toBe("-p-");
|
expect(unrarRename[2]).toBe("-p-");
|
||||||
expect(unrarRename[3]).toBe("-y");
|
expect(unrarRename[3]).toBe("-y");
|
||||||
expect(unrarRename[unrarRename.length - 2]).toBe("archive.rar");
|
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 () => {
|
it("deletes only successfully extracted archives", async () => {
|
||||||
@ -1174,13 +1179,13 @@ describe("extractor", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("orderExtractorCandidatesForArchive", () => {
|
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(
|
const ordered = orderExtractorCandidatesForArchive(
|
||||||
["7z.exe", "UnRAR.exe", "WinRAR.exe"],
|
["7z.exe", "Rar.exe", "UnRAR.exe", "WinRAR.exe"],
|
||||||
"C:\\Downloads\\archive.part01.rar"
|
"C:\\Downloads\\archive.part01.rar"
|
||||||
);
|
);
|
||||||
expect(ordered.slice(0, 2)).toEqual(["UnRAR.exe", "WinRAR.exe"]);
|
expect(ordered.slice(0, 3)).toEqual(["Rar.exe", "UnRAR.exe", "WinRAR.exe"]);
|
||||||
expect(ordered[2]).toBe("7z.exe");
|
expect(ordered[3]).toBe("7z.exe");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps 7-Zip first for non-rar archives", () => {
|
it("keeps 7-Zip first for non-rar archives", () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user