fix: reconcile native extraction outputs
Use the validated native archive plan to recognize exact files after successful WinRAR and 7-Zip runs even when progress output contains no parseable completion lines. Preserve extractor-specific rename targets across subst mappings, keep 7-Zip directory paths when RAR flat mode is active, and reject colliding RAR flat targets before extraction.
This commit is contained in:
+121
-2
@@ -21,11 +21,14 @@ import {
|
||||
orderExtractorCandidatesForArchive,
|
||||
parseNativeExtractOutput,
|
||||
parseNativeArchiveEntryList,
|
||||
resolveExtractorBackendModeForArchive,
|
||||
remapNativeSubstOutput,
|
||||
reconcileNativeExtractOutputs,
|
||||
resolveExtractorBackendModeForArchive,
|
||||
resolveExtractorBackendMode,
|
||||
shouldFallbackLegacyRarToJvm,
|
||||
validateNativeArchiveEntryCandidates,
|
||||
} from "../src/main/extractor";
|
||||
validateNativeFlatArchiveEntryCandidates,
|
||||
} from "../src/main/extractor";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
const originalExtractBackend = process.env.RD_EXTRACT_BACKEND;
|
||||
@@ -1731,6 +1734,56 @@ describe("extractor", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("preserves a native renamed filename when remapping a subst output", () => {
|
||||
const targetDir = "C:\\Downloads\\Extracted";
|
||||
const event = remapNativeSubstOutput({
|
||||
version: 1,
|
||||
archivePath: "C:\\Downloads\\release.rar",
|
||||
entryPath: "episode.mkv",
|
||||
outputPath: "Z:\\episode(2).mkv",
|
||||
state: "complete",
|
||||
disposition: "renamed"
|
||||
}, "Z:\\", targetDir);
|
||||
|
||||
expect(event.outputPath).toBe(path.resolve(targetDir, "episode(2).mkv"));
|
||||
expect(event.entryPath).toBe("episode.mkv");
|
||||
});
|
||||
|
||||
it("keeps native 7-Zip output directories when a previous RAR requested flat mode", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-native-seven-flat-"));
|
||||
tempDirs.push(root);
|
||||
const targetDir = path.join(root, "out");
|
||||
const outputPath = path.join(targetDir, "folder", "episode.mkv");
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, "video");
|
||||
|
||||
const events = reconcileNativeExtractOutputs(
|
||||
"7z.exe",
|
||||
[{ entryPath: "folder/episode.mkv", isDirectory: false }],
|
||||
path.join(root, "release.7z"),
|
||||
targetDir,
|
||||
"overwrite",
|
||||
new Set(),
|
||||
true
|
||||
);
|
||||
|
||||
expect(events).toEqual([
|
||||
expect.objectContaining({ entryPath: "folder/episode.mkv", outputPath })
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects colliding RAR flat-mode basenames before extraction", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-native-rar-flat-collision-"));
|
||||
tempDirs.push(root);
|
||||
const targetDir = path.join(root, "out");
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
|
||||
expect(() => validateNativeFlatArchiveEntryCandidates("Rar.exe", [
|
||||
"folder-a/episode.mkv",
|
||||
"folder-b/episode.mkv"
|
||||
], targetDir)).toThrow(/Kollision/i);
|
||||
});
|
||||
|
||||
it.each(["Entpacke", "Extrayendo", "Extraction"])("parses verified native RAR candidates without depending on the %s locale verb", (verb) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-native-locale-"));
|
||||
tempDirs.push(root);
|
||||
@@ -1744,6 +1797,72 @@ describe("extractor", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("reconciles verified native outputs when WinRAR emits no parseable completion line", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-native-reconcile-"));
|
||||
tempDirs.push(root);
|
||||
const targetDir = path.join(root, "out");
|
||||
const archivePath = path.join(root, "release.part1.rar");
|
||||
const outputPath = path.join(targetDir, "folder", "episode.mkv");
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, "video");
|
||||
|
||||
const events = reconcileNativeExtractOutputs(
|
||||
"Rar.exe",
|
||||
[{ entryPath: "folder/", isDirectory: true }, { entryPath: "folder/episode.mkv", isDirectory: false }],
|
||||
archivePath,
|
||||
targetDir,
|
||||
"overwrite",
|
||||
new Set()
|
||||
);
|
||||
|
||||
expect(events).toEqual([{
|
||||
version: 1,
|
||||
archivePath: path.resolve(archivePath),
|
||||
entryPath: "folder/episode.mkv",
|
||||
outputPath,
|
||||
state: "complete",
|
||||
disposition: "written"
|
||||
}]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["Rar.exe", "episode(1).mkv", "episode(2).mkv"],
|
||||
["7z.exe", "episode_1.mkv", "episode_2.mkv"]
|
||||
])("reconciles the newly renamed %s output without claiming an older collision", (command, firstRenamedName, newRenamedName) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-native-rename-reconcile-"));
|
||||
tempDirs.push(root);
|
||||
const targetDir = path.join(root, "out");
|
||||
const archivePath = path.join(root, "release.rar");
|
||||
const basePath = path.join(targetDir, "episode.mkv");
|
||||
const firstRenamedPath = path.join(targetDir, firstRenamedName);
|
||||
const newRenamedPath = path.join(targetDir, newRenamedName);
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
fs.writeFileSync(basePath, "foreign-base");
|
||||
fs.writeFileSync(firstRenamedPath, "foreign-renamed");
|
||||
const existingBefore = new Set([basePath, firstRenamedPath].map((filePath) => (
|
||||
process.platform === "win32" ? path.resolve(filePath).toLowerCase() : path.resolve(filePath)
|
||||
)));
|
||||
fs.writeFileSync(newRenamedPath, "owned");
|
||||
|
||||
const events = reconcileNativeExtractOutputs(
|
||||
command,
|
||||
[{ entryPath: "episode.mkv", isDirectory: false }],
|
||||
archivePath,
|
||||
targetDir,
|
||||
"rename",
|
||||
existingBefore
|
||||
);
|
||||
|
||||
expect(events).toEqual([
|
||||
expect.objectContaining({
|
||||
entryPath: "episode.mkv",
|
||||
outputPath: newRenamedPath,
|
||||
state: "complete",
|
||||
disposition: "renamed"
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["file.mkv:stream", "file.mkv"],
|
||||
["name.", "name"],
|
||||
|
||||
Reference in New Issue
Block a user