Fix: Dedup-Lifecycle der Benachrichtigungen — Recovery/Retry/Trailing-Pakete
Audit-Befunde DEDUP-1 (HIGH), DEDUP-3, DEDUP-4, NOTIFY-DEDUP-BEFORE-CONFIRM: - DEDUP-1: recoverRetryableItems (Auto-Requeue gefailter Items beim Start) loescht jetzt den notifiedPackages-Eintrag der betroffenen Pakete. Vorher blieb der Failed-Marker aus Run 1 kleben — die Erfolgs-Benachrichtigung nach geglueckter Recovery in Run 2 (genau die Nachricht, auf die man wartet) kam nie; in Discord blieb das Paket fuer immer ❌. - DEDUP-3: retryExtraction/extractNow loeschen den Dedup-Marker und treten dem Run UNBEDINGT bei (runPackageIds.add) — der korrigierende ✅ nach manuell wiederholtem Entpacken kam sonst nie an, v.a. wenn der Run schon vorbei war. History-Dedup bewusst unangetastet (sonst doppelte History-Eintraege). - DEDUP-4: start()/startPackages()/startItems() ersetzen runPackageIds; Pakete mit noch LAUFENDEM Post-Processing (Task, Deferred, Hybrid) flogen aus dem Set und ihre Abschluss-Benachrichtigung wurde nach dem naechsten finishRun verworfen (autoExtractWhenStopped beendet den Run ~sofort). Neuer Helper addTrailingPostProcessPackageIds ergaenzt das neue Set an allen 5 Stellen — inkl. der Start-ohne-Queue-Pfade, die sogar die GERADE angestossenen Entpackungen aus dem Set warfen. - Dedup-Marker wird wieder freigegeben, wenn der Versand (nach den Sender- Retries) endgueltig scheiterte — ein transienter Ausfall verbraucht den Einmal-Slot nicht mehr dauerhaft.
This commit is contained in:
parent
99a24592c5
commit
8acb22d3af
@ -4661,6 +4661,29 @@ export class DownloadManager extends EventEmitter {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Packages whose post-processing (task, deferred pass or hybrid round) is still
|
||||
// alive must keep their run-membership when the run set is replaced/cleared —
|
||||
// otherwise their terminal notification is dropped as soon as session.running
|
||||
// flips false (the notify guard checks running || runPackageIds).
|
||||
private addTrailingPostProcessPackageIds(target: Set<string>): void {
|
||||
for (const id of this.packagePostProcessTasks.keys()) {
|
||||
target.add(id);
|
||||
}
|
||||
for (const [id, controller] of this.packageDeferredPostProcessAbortControllers) {
|
||||
if (!controller.signal.aborted) {
|
||||
target.add(id);
|
||||
}
|
||||
}
|
||||
for (const [id, hybridSet] of this.packageHybridPostProcessControllers) {
|
||||
for (const c of hybridSet) {
|
||||
if (!c.signal.aborted) {
|
||||
target.add(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private buildCollectFolderCandidates(sourcePath: string, sourceRoot: string, pkg: PackageEntry): string[] {
|
||||
const folderCandidates: string[] = [];
|
||||
let currentDir = path.dirname(sourcePath);
|
||||
@ -5326,6 +5349,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
this.runItemIds = new Set(runItems.map((item) => item.id));
|
||||
this.runPackageIds = new Set(runItems.map((item) => item.packageId));
|
||||
this.addTrailingPostProcessPackageIds(this.runPackageIds);
|
||||
this.runOutcomes.clear();
|
||||
this.runCompletedPackages.clear();
|
||||
this.retryAfterByItem.clear();
|
||||
@ -5430,6 +5454,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
this.runItemIds = new Set(runItems.map((item) => item.id));
|
||||
this.runPackageIds = new Set(runItems.map((item) => item.packageId));
|
||||
this.addTrailingPostProcessPackageIds(this.runPackageIds);
|
||||
this.runOutcomes.clear();
|
||||
this.runCompletedPackages.clear();
|
||||
this.retryAfterByItem.clear();
|
||||
@ -5519,6 +5544,7 @@ export class DownloadManager extends EventEmitter {
|
||||
if (this.packagePostProcessTasks.size > 0) {
|
||||
this.runItemIds.clear();
|
||||
this.runPackageIds.clear();
|
||||
this.addTrailingPostProcessPackageIds(this.runPackageIds);
|
||||
this.runOutcomes.clear();
|
||||
this.runCompletedPackages.clear();
|
||||
this.session.running = true;
|
||||
@ -5537,6 +5563,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
this.runItemIds.clear();
|
||||
this.runPackageIds.clear();
|
||||
this.addTrailingPostProcessPackageIds(this.runPackageIds);
|
||||
this.runOutcomes.clear();
|
||||
this.runCompletedPackages.clear();
|
||||
this.retryAfterByItem.clear();
|
||||
@ -5567,6 +5594,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
this.runItemIds = new Set(runItems.map((item) => item.id));
|
||||
this.runPackageIds = new Set(runItems.map((item) => item.packageId));
|
||||
this.addTrailingPostProcessPackageIds(this.runPackageIds);
|
||||
this.runOutcomes.clear();
|
||||
this.runCompletedPackages.clear();
|
||||
this.retryAfterByItem.clear();
|
||||
@ -7499,6 +7527,11 @@ export class DownloadManager extends EventEmitter {
|
||||
completedItems: completedItems.length,
|
||||
targetedItems: targetItems.length
|
||||
});
|
||||
// Fresh outcome must notify again (the corrective checkmark after a notified
|
||||
// extraction failure); unconditional run-membership so the notify guard also
|
||||
// passes when the retry happens after the run already ended.
|
||||
this.notifiedPackages.delete(packageId);
|
||||
this.runPackageIds.add(packageId);
|
||||
this.persistSoon();
|
||||
this.emitState(true);
|
||||
void this.runPackagePostProcessing(packageId).catch((err) => logger.warn(`runPackagePostProcessing Fehler (retryExtraction): ${compactErrorText(err)}`));
|
||||
@ -7527,6 +7560,8 @@ export class DownloadManager extends EventEmitter {
|
||||
completedItems: completedItems.length,
|
||||
targetedItems: targetItems.length
|
||||
});
|
||||
this.notifiedPackages.delete(packageId);
|
||||
this.runPackageIds.add(packageId);
|
||||
this.persistSoon();
|
||||
this.emitState(true);
|
||||
void this.runPackagePostProcessing(packageId).catch((err) => logger.warn(`runPackagePostProcessing Fehler (extractNow): ${compactErrorText(err)}`));
|
||||
@ -10419,6 +10454,10 @@ export class DownloadManager extends EventEmitter {
|
||||
if (!pkg) {
|
||||
continue;
|
||||
}
|
||||
// The package gets a fresh chance — its next outcome must notify again
|
||||
// (a recovery success after a notified failure is the message the user
|
||||
// most wants). History dedup stays untouched (separate semantics).
|
||||
this.notifiedPackages.delete(packageId);
|
||||
this.refreshPackageStatus(pkg);
|
||||
}
|
||||
logger.warn(
|
||||
@ -10503,10 +10542,17 @@ export class DownloadManager extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
this.notifiedPackages.add(pkg.id);
|
||||
// Release the dedup marker if the delivery ultimately failed (after the
|
||||
// sender's own retries), so a later manual re-run can notify again instead
|
||||
// of a transient outage permanently consuming the once-per-package slot.
|
||||
void sendNotification(url, {
|
||||
title: kind === "completed" ? "✅ Paket fertig" : "❌ Paket fehlgeschlagen",
|
||||
message: `${pkg.name}\n${detail}`,
|
||||
mention: this.settings.notifyMention
|
||||
}).then((ok) => {
|
||||
if (!ok) {
|
||||
this.notifiedPackages.delete(pkg.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user