Fix extract error status bleed

This commit is contained in:
Sucukdeluxe 2026-03-09 19:43:35 +01:00
parent 2aa6516b37
commit 446c41a9b3
2 changed files with 139 additions and 7 deletions

View File

@ -5257,6 +5257,64 @@ export class DownloadManager extends EventEmitter {
return changed;
}
private applyPackageExtractFailureStatuses(
completedItems: DownloadItem[],
resolveArchiveItems: (archiveName: string) => DownloadItem[],
failedArchiveErrors: Map<string, string>,
fallbackReason: string,
previousStatuses: Map<string, string>,
appliedAt = nowMs()
): void {
const affectedItemIds = new Set<string>();
for (const [archiveName, errorText] of failedArchiveErrors.entries()) {
const reason = compactErrorText(errorText || fallbackReason || "Entpacken fehlgeschlagen");
for (const entry of resolveArchiveItems(archiveName)) {
if (entry.status !== "completed" || isExtractedLabel(entry.fullStatus)) {
continue;
}
entry.fullStatus = `Entpack-Fehler: ${reason}`;
entry.updatedAt = appliedAt;
affectedItemIds.add(entry.id);
}
}
let appliedSpecificFailure = affectedItemIds.size > 0;
for (const entry of completedItems) {
if (entry.status !== "completed" || isExtractedLabel(entry.fullStatus)) {
continue;
}
if (affectedItemIds.has(entry.id)) {
continue;
}
const currentStatus = String(entry.fullStatus || "").trim();
if (currentStatus === "Entpacken - Error") {
entry.fullStatus = `Entpack-Fehler: ${fallbackReason}`;
entry.updatedAt = appliedAt;
appliedSpecificFailure = true;
continue;
}
if (/^Entpacken\b/i.test(currentStatus) || /^Passwort\b/i.test(currentStatus) || /^Finalisieren\b/i.test(currentStatus)) {
const previousStatus = String(previousStatuses.get(entry.id) || "").trim();
entry.fullStatus = previousStatus || `Fertig (${humanSize(entry.downloadedBytes)})`;
entry.updatedAt = appliedAt;
}
}
if (appliedSpecificFailure) {
return;
}
for (const entry of completedItems) {
if (entry.status === "completed" && !isExtractedLabel(entry.fullStatus)) {
entry.fullStatus = `Entpack-Fehler: ${fallbackReason}`;
entry.updatedAt = appliedAt;
}
}
}
private async waitForCompletedArchiveFilesToSettle(
pkg: PackageEntry,
items: DownloadItem[],
@ -9645,6 +9703,7 @@ export class DownloadManager extends EventEmitter {
pkg.status = "extracting";
this.emitState();
const extractionStartMs = nowMs();
const preExtractStatuses = new Map<string, string>();
const resolveArchiveItems = (archiveName: string): DownloadItem[] =>
resolveArchiveItemsFromList(archiveName, completedItems);
@ -9663,6 +9722,7 @@ export class DownloadManager extends EventEmitter {
// Mark all items as pending before extraction starts
for (const entry of completedItems) {
if (!isExtractedLabel(entry.fullStatus)) {
preExtractStatuses.set(entry.id, String(entry.fullStatus || "").trim());
entry.fullStatus = "Entpacken - Ausstehend";
entry.updatedAt = nowMs();
}
@ -9698,6 +9758,7 @@ export class DownloadManager extends EventEmitter {
try {
// Track archives for parallel extraction progress
const autoRecoveredArchives = new Set<string>();
const fullFailedArchiveErrors = new Map<string, string>();
const fullResolvedItems = new Map<string, DownloadItem[]>();
const fullStartTimes = new Map<string, number>();
let fullLastProgressCurrent: number | null = null;
@ -9737,7 +9798,13 @@ export class DownloadManager extends EventEmitter {
const changed = this.autoRecoverArchiveCrcFailure(pkg, completedItems, failure, "full");
if (changed > 0) {
autoRecoveredArchives.add(failure.archiveName);
fullFailedArchiveErrors.delete(failure.archiveName);
return;
}
fullFailedArchiveErrors.set(
failure.archiveName,
failure.errorText || failure.jvmFailureReason || "Entpacken fehlgeschlagen"
);
},
onProgress: (progress) => {
if (progress.phase === "preparing") {
@ -9872,13 +9939,14 @@ export class DownloadManager extends EventEmitter {
if (result.failed > 0) {
const reason = compactErrorText(result.lastError || "Entpacken fehlgeschlagen");
const failAt = nowMs();
for (const entry of completedItems) {
// Preserve per-archive "Entpackt - Done (X.Xs)" labels for successfully extracted archives
if (entry.status === "completed" && !isExtractedLabel(entry.fullStatus)) {
entry.fullStatus = `Entpack-Fehler: ${reason}`;
entry.updatedAt = failAt;
}
}
this.applyPackageExtractFailureStatuses(
completedItems,
resolveArchiveItems,
fullFailedArchiveErrors,
reason,
preExtractStatuses,
failAt
);
pkg.status = "failed";
} else {
const hasExtractedOutput = await this.directoryHasAnyFiles(pkg.extractDir);

View File

@ -3840,6 +3840,70 @@ describe("download manager", () => {
expect((manager as any).session.items[itemId].fullStatus).toBe("Entpack-Fehler: Checksum error in encrypted file");
});
it("applies final extract errors only to the affected full-extract archive items", () => {
const createdAt = Date.now() - 10_000;
const completedItems = [
{
id: "full-fail-item-1",
status: "completed",
fileName: "show.s01e01.part1.rar",
downloadedBytes: 100 * 1024 * 1024,
fullStatus: "Fertig (100 MB)",
updatedAt: createdAt
},
{
id: "full-fail-item-2",
status: "completed",
fileName: "show.s01e01.part2.rar",
downloadedBytes: 100 * 1024 * 1024,
fullStatus: "Fertig (100 MB)",
updatedAt: createdAt
},
{
id: "full-fail-item-3",
status: "completed",
fileName: "show.s01e02.part1.rar",
downloadedBytes: 200 * 1024 * 1024,
fullStatus: "Fertig (200 MB)",
updatedAt: createdAt
},
{
id: "full-fail-item-4",
status: "completed",
fileName: "show.s01e02.part2.rar",
downloadedBytes: 200 * 1024 * 1024,
fullStatus: "Fertig (200 MB)",
updatedAt: createdAt
}
] as any[];
const previousStatuses = new Map<string, string>(completedItems.map((item: any) => [item.id, item.fullStatus]));
for (const item of completedItems) {
item.fullStatus = "Entpacken - Ausstehend";
}
completedItems[0].fullStatus = "Entpacken - Error";
completedItems[1].fullStatus = "Entpacken - Error";
const resolveArchiveItems = (archiveName: string) => {
const base = archiveName.replace(/\.part0*1\.rar$/i, "");
return completedItems.filter((item: any) => String(item.fileName || "").toLowerCase().startsWith(`${base}.part`));
};
(DownloadManager.prototype as any).applyPackageExtractFailureStatuses.call(
{},
completedItems,
resolveArchiveItems,
new Map([["show.s01e01.part1.rar", "Checksum error in the encrypted file"]]),
"Checksum error in the encrypted file",
previousStatuses,
createdAt + 5_000
);
expect(completedItems[0].fullStatus).toBe("Entpack-Fehler: Checksum error in the encrypted file");
expect(completedItems[1].fullStatus).toBe("Entpack-Fehler: Checksum error in the encrypted file");
expect(completedItems[2].fullStatus).toBe("Fertig (200 MB)");
expect(completedItems[3].fullStatus).toBe("Fertig (200 MB)");
});
it("detects start conflicts when extract output already exists", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
tempDirs.push(root);