diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 5a696ac..3df8a80 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -5249,24 +5249,26 @@ export class DownloadManager extends EventEmitter { const pkg = this.session.packages[pkgId]; if (pkg) this.refreshPackageStatus(pkg); } - if (this.settings.autoExtract) { - for (const pkgId of affectedPackageIds) { - const pkg = this.session.packages[pkgId]; - if (!pkg || pkg.cancelled || this.packagePostProcessTasks.has(pkgId)) continue; - const pkgItems = pkg.itemIds.map((id) => this.session.items[id]).filter(Boolean) as DownloadItem[]; - const hasPending = pkgItems.some((i) => i.status !== "completed" && i.status !== "failed" && i.status !== "cancelled"); - const hasFailed = pkgItems.some((i) => i.status === "failed"); - const hasUnextracted = pkgItems.some((i) => i.status === "completed" && shouldAutoRetryExtraction(i.fullStatus || "")); - if (!hasPending && !hasFailed && hasUnextracted) { - for (const it of pkgItems) { - if (it.status === "completed" && shouldAutoRetryExtraction(it.fullStatus || "")) { - it.fullStatus = "Entpacken - Ausstehend"; - it.updatedAt = nowMs(); - } + // A skip can be the package's LAST terminal event; without this trigger the + // post-processing terminal block (notify, history, cleanup policy) never runs + // for it — earlier rounds returned while the skipped item was still pending. + for (const pkgId of affectedPackageIds) { + const pkg = this.session.packages[pkgId]; + if (!pkg || pkg.cancelled || this.packagePostProcessTasks.has(pkgId)) continue; + const pkgItems = pkg.itemIds.map((id) => this.session.items[id]).filter(Boolean) as DownloadItem[]; + const hasPending = pkgItems.some((i) => i.status !== "completed" && i.status !== "failed" && i.status !== "cancelled"); + if (hasPending) continue; + const hasFailed = pkgItems.some((i) => i.status === "failed"); + const hasUnextracted = pkgItems.some((i) => i.status === "completed" && shouldAutoRetryExtraction(i.fullStatus || "")); + if (this.settings.autoExtract && !hasFailed && hasUnextracted) { + for (const it of pkgItems) { + if (it.status === "completed" && shouldAutoRetryExtraction(it.fullStatus || "")) { + it.fullStatus = "Entpacken - Ausstehend"; + it.updatedAt = nowMs(); } - void this.runPackagePostProcessing(pkgId).catch((err) => logger.warn(`Post-processing nach Skip: ${compactErrorText(err)}`)); } } + void this.runPackagePostProcessing(pkgId).catch((err) => logger.warn(`Post-processing nach Skip: ${compactErrorText(err)}`)); } this.persistSoon(); this.emitState(); @@ -9125,6 +9127,8 @@ export class DownloadManager extends EventEmitter { item.fullStatus = `Fehler: ${item.lastError}`; item.speedBps = 0; item.updatedAt = nowMs(); + const failPkg416 = this.session.packages[item.packageId]; + if (failPkg416) this.refreshPackageStatus(failPkg416); this.persistSoon(); this.emitState(); this.retryStateByItem.delete(item.id); @@ -9161,6 +9165,8 @@ export class DownloadManager extends EventEmitter { item.speedBps = 0; item.updatedAt = nowMs(); this.retryStateByItem.delete(item.id); + const failPkgDead = this.session.packages[item.packageId]; + if (failPkgDead) this.refreshPackageStatus(failPkgDead); this.persistSoon(); this.emitState(); return; @@ -9219,6 +9225,8 @@ export class DownloadManager extends EventEmitter { item.speedBps = 0; item.updatedAt = nowMs(); this.retryStateByItem.delete(item.id); + const failPkgDl = this.session.packages[item.packageId]; + if (failPkgDl) this.refreshPackageStatus(failPkgDl); this.persistSoon(); this.emitState(); return; @@ -10544,10 +10552,17 @@ export class DownloadManager extends EventEmitter { pkg.status = "completed"; } pkg.updatedAt = nowMs(); - // A package whose items ALL failed never enters post-processing, so the - // post-process notify hook can't fire for it — cover that case here. - if (pkg.status === "failed" && prevStatus !== "failed" && success === 0) { + // A package whose LAST terminal event is a failure never (re-)enters + // post-processing (its trigger sits only on completion paths), so the + // post-process notify hook can't fire — cover every failed-transition here. + // That includes mixed packages (success > 0): the dedup set prevents a + // double-fire if post-processing does run later. + if (pkg.status === "failed" && prevStatus !== "failed") { this.notifyPackageOutcome(pkg, "failed", `${failed} von ${total} Datei(en) fehlgeschlagen`); + if (success > 0) { + const items = pkg.itemIds.map((id) => this.session.items[id]).filter(Boolean) as DownloadItem[]; + this.recordPackageHistory(pkg.id, pkg, items); + } } }