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;
|
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[] {
|
private buildCollectFolderCandidates(sourcePath: string, sourceRoot: string, pkg: PackageEntry): string[] {
|
||||||
const folderCandidates: string[] = [];
|
const folderCandidates: string[] = [];
|
||||||
let currentDir = path.dirname(sourcePath);
|
let currentDir = path.dirname(sourcePath);
|
||||||
@ -5326,6 +5349,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.runItemIds = new Set(runItems.map((item) => item.id));
|
this.runItemIds = new Set(runItems.map((item) => item.id));
|
||||||
this.runPackageIds = new Set(runItems.map((item) => item.packageId));
|
this.runPackageIds = new Set(runItems.map((item) => item.packageId));
|
||||||
|
this.addTrailingPostProcessPackageIds(this.runPackageIds);
|
||||||
this.runOutcomes.clear();
|
this.runOutcomes.clear();
|
||||||
this.runCompletedPackages.clear();
|
this.runCompletedPackages.clear();
|
||||||
this.retryAfterByItem.clear();
|
this.retryAfterByItem.clear();
|
||||||
@ -5430,6 +5454,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.runItemIds = new Set(runItems.map((item) => item.id));
|
this.runItemIds = new Set(runItems.map((item) => item.id));
|
||||||
this.runPackageIds = new Set(runItems.map((item) => item.packageId));
|
this.runPackageIds = new Set(runItems.map((item) => item.packageId));
|
||||||
|
this.addTrailingPostProcessPackageIds(this.runPackageIds);
|
||||||
this.runOutcomes.clear();
|
this.runOutcomes.clear();
|
||||||
this.runCompletedPackages.clear();
|
this.runCompletedPackages.clear();
|
||||||
this.retryAfterByItem.clear();
|
this.retryAfterByItem.clear();
|
||||||
@ -5519,6 +5544,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (this.packagePostProcessTasks.size > 0) {
|
if (this.packagePostProcessTasks.size > 0) {
|
||||||
this.runItemIds.clear();
|
this.runItemIds.clear();
|
||||||
this.runPackageIds.clear();
|
this.runPackageIds.clear();
|
||||||
|
this.addTrailingPostProcessPackageIds(this.runPackageIds);
|
||||||
this.runOutcomes.clear();
|
this.runOutcomes.clear();
|
||||||
this.runCompletedPackages.clear();
|
this.runCompletedPackages.clear();
|
||||||
this.session.running = true;
|
this.session.running = true;
|
||||||
@ -5537,6 +5563,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.runItemIds.clear();
|
this.runItemIds.clear();
|
||||||
this.runPackageIds.clear();
|
this.runPackageIds.clear();
|
||||||
|
this.addTrailingPostProcessPackageIds(this.runPackageIds);
|
||||||
this.runOutcomes.clear();
|
this.runOutcomes.clear();
|
||||||
this.runCompletedPackages.clear();
|
this.runCompletedPackages.clear();
|
||||||
this.retryAfterByItem.clear();
|
this.retryAfterByItem.clear();
|
||||||
@ -5567,6 +5594,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.runItemIds = new Set(runItems.map((item) => item.id));
|
this.runItemIds = new Set(runItems.map((item) => item.id));
|
||||||
this.runPackageIds = new Set(runItems.map((item) => item.packageId));
|
this.runPackageIds = new Set(runItems.map((item) => item.packageId));
|
||||||
|
this.addTrailingPostProcessPackageIds(this.runPackageIds);
|
||||||
this.runOutcomes.clear();
|
this.runOutcomes.clear();
|
||||||
this.runCompletedPackages.clear();
|
this.runCompletedPackages.clear();
|
||||||
this.retryAfterByItem.clear();
|
this.retryAfterByItem.clear();
|
||||||
@ -7499,6 +7527,11 @@ export class DownloadManager extends EventEmitter {
|
|||||||
completedItems: completedItems.length,
|
completedItems: completedItems.length,
|
||||||
targetedItems: targetItems.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.persistSoon();
|
||||||
this.emitState(true);
|
this.emitState(true);
|
||||||
void this.runPackagePostProcessing(packageId).catch((err) => logger.warn(`runPackagePostProcessing Fehler (retryExtraction): ${compactErrorText(err)}`));
|
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,
|
completedItems: completedItems.length,
|
||||||
targetedItems: targetItems.length
|
targetedItems: targetItems.length
|
||||||
});
|
});
|
||||||
|
this.notifiedPackages.delete(packageId);
|
||||||
|
this.runPackageIds.add(packageId);
|
||||||
this.persistSoon();
|
this.persistSoon();
|
||||||
this.emitState(true);
|
this.emitState(true);
|
||||||
void this.runPackagePostProcessing(packageId).catch((err) => logger.warn(`runPackagePostProcessing Fehler (extractNow): ${compactErrorText(err)}`));
|
void this.runPackagePostProcessing(packageId).catch((err) => logger.warn(`runPackagePostProcessing Fehler (extractNow): ${compactErrorText(err)}`));
|
||||||
@ -10419,6 +10454,10 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (!pkg) {
|
if (!pkg) {
|
||||||
continue;
|
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);
|
this.refreshPackageStatus(pkg);
|
||||||
}
|
}
|
||||||
logger.warn(
|
logger.warn(
|
||||||
@ -10503,10 +10542,17 @@ export class DownloadManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.notifiedPackages.add(pkg.id);
|
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, {
|
void sendNotification(url, {
|
||||||
title: kind === "completed" ? "✅ Paket fertig" : "❌ Paket fehlgeschlagen",
|
title: kind === "completed" ? "✅ Paket fertig" : "❌ Paket fehlgeschlagen",
|
||||||
message: `${pkg.name}\n${detail}`,
|
message: `${pkg.name}\n${detail}`,
|
||||||
mention: this.settings.notifyMention
|
mention: this.settings.notifyMention
|
||||||
|
}).then((ok) => {
|
||||||
|
if (!ok) {
|
||||||
|
this.notifiedPackages.delete(pkg.id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user