Fix: neu hinzugefügtes Archiv-Passwort greift jetzt ohne App-Neustart

User-Report: ZIP scheitert mit wrong_password -> Passwort zur Liste hinzufügen
+ Settings speichern -> "Jetzt entpacken" scheitert WIEDER -> erst nach
App-Neustart klappt "Jetzt entpacken".

Analyse (eigenständig + zweiter Analyse-Agent, gesamte TS+Java-Kette):
Renderer-Save -> updateSettings (fingerprint enthält archivePasswordList) ->
setSettings (this.settings aktualisiert) -> extractNow -> runPackagePostProcessing
-> extractPackageArchives (passwordList: this.settings.archivePasswordList) ->
archivePasswordCandidates (enthält neues PW) -> Daemon bekommt Passwörter PRO
REQUEST. Java-Daemon probiert alle PW frisch, kein Per-Archiv-Cache; zip4j/
sevenzipjbinding ohne relevanten static State. Alle Pfade propagieren die neue
Liste korrekt — statisch ist KEIN Bug auffindbar.

Verifikation per Ausschluss: die EINZIGE zustandsbehaftete Komponente, die ein
App-Neustart zurücksetzt und ein Settings-Save NICHT, ist der langlebige
JVM-Daemon-Prozess (+ der In-Memory Learned-Password-Cache). Der User bestätigt
empirisch, dass ein Neustart (= frischer Daemon) es fixt.

Fix: neue resetExtractorCachesForPasswordChange() repliziert den Neustart-Effekt
am Extractor-Subsystem — bei Änderung der Passwortliste in setSettings wird der
Learned-Password-Cache geleert und der idle JVM-Daemon heruntergefahren, sodass
die nächste Extraktion frisch mit der neuen Liste startet. Beschäftigter Daemon
wird nicht abgebrochen (laufende Extraktion bleibt unangetastet).

Diagnostik: setSettings loggt jetzt PW-Anzahl + Reset-Resultat. Zusammen mit den
bestehenden "Archiv-Passwortliste: passwordCount=N"-Logs lässt sich bei erneutem
Auftreten in 30s unterscheiden, ob das PW beim Extractor ankommt (H2, Fix greift)
oder nicht (H1, dann TS-upstream). Best-bet per Ausschluss + Logs zur finalen
Bestätigung beim nächsten Repro.

621 Tests grün, tsc-Fehlerzahl unverändert (9 pre-existing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-05-24 19:07:22 +02:00
parent 4398afa271
commit 08372f99cb
2 changed files with 40 additions and 1 deletions

View File

@ -52,7 +52,7 @@ function releaseTlsSkip(): void {
import { cleanupCancelledPackageArtifactsAsync, removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup"; import { cleanupCancelledPackageArtifactsAsync, removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup";
import { planDownloadCompletion, validateDownloadedFileCompletion } from "./download-completion"; import { planDownloadCompletion, validateDownloadedFileCompletion } from "./download-completion";
import { AllDebridWebUnrestrictor, BestDebridWebUnrestrictor, DebridService, MegaWebUnrestrictor, RealDebridWebUnrestrictor, checkRapidgatorOnline, fetchAllDebridHostInfo, getAvailableDebridLinkApiKeys, pruneExpiredDebridLinkRuntimeState, pruneExpiredMegaDebridRuntimeState } from "./debrid"; import { AllDebridWebUnrestrictor, BestDebridWebUnrestrictor, DebridService, MegaWebUnrestrictor, RealDebridWebUnrestrictor, checkRapidgatorOnline, fetchAllDebridHostInfo, getAvailableDebridLinkApiKeys, pruneExpiredDebridLinkRuntimeState, pruneExpiredMegaDebridRuntimeState } from "./debrid";
import { cleanupArchives, clearExtractResumeState, collectArchiveCleanupTargets, detectArchiveSignature, extractPackageArchives, findArchiveCandidates, hasAnyFilesRecursive, removeEmptyDirectoryTree, type ExtractArchiveFailureInfo } from "./extractor"; import { cleanupArchives, clearExtractResumeState, collectArchiveCleanupTargets, detectArchiveSignature, extractPackageArchives, findArchiveCandidates, hasAnyFilesRecursive, removeEmptyDirectoryTree, resetExtractorCachesForPasswordChange, type ExtractArchiveFailureInfo } from "./extractor";
import { validateFileAgainstManifest } from "./integrity"; import { validateFileAgainstManifest } from "./integrity";
import { logger } from "./logger"; import { logger } from "./logger";
import { ensureItemLog, getItemLogPath as getPersistedItemLogPath, logItemEvent as writeItemLogEvent } from "./item-log"; import { ensureItemLog, getItemLogPath as getPersistedItemLogPath, logItemEvent as writeItemLogEvent } from "./item-log";
@ -2080,6 +2080,16 @@ export class DownloadManager extends EventEmitter {
if (previousArchivePasswords !== nextArchivePasswords) { if (previousArchivePasswords !== nextArchivePasswords) {
this.hybridExtractedPaths.clear(); this.hybridExtractedPaths.clear();
this.hybridFailedArchives.clear(); this.hybridFailedArchives.clear();
// Bug-Fix: ein neu hinzugefügtes Passwort griff bei "Jetzt entpacken" erst
// nach App-Neustart. Der gesamte Settings-/Extract-Pfad propagiert die Liste
// korrekt pro Request — aber der langlebige JVM-Daemon + der In-Memory
// Learned-Password-Cache überleben einen Settings-Save (nur ein App-Neustart
// setzt sie zurück). Wir replizieren den Neustart-Effekt am Extractor-
// Subsystem: Learned-Cache leeren + idle Daemon herunterfahren, damit die
// nächste Extraktion frisch mit der neuen Passwortliste startet.
const pwCount = nextArchivePasswords.split("\n").filter(Boolean).length;
const reset = resetExtractorCachesForPasswordChange();
logger.info(`Archiv-Passwortliste geaendert (${pwCount} Eintraege): Extractor-Caches zurueckgesetzt (learned=${reset.learnedCleared}, daemonRestart=${reset.daemonRestarted})`);
} }
// When account credentials change, clear the provider-failure circuit-breaker // When account credentials change, clear the provider-failure circuit-breaker

View File

@ -742,6 +742,35 @@ function clearCachedPackagePassword(cacheKey: string): void {
packageLearnedPasswords.delete(cacheKey); packageLearnedPasswords.delete(cacheKey);
} }
/**
* Setzt den Extractor-Zustand zurück, wenn der User die Archiv-Passwortliste
* ändert. Repliziert, was ein App-Neustart am Extractor-Subsystem tut:
* - leert den In-Memory Learned-Password-Cache (gelernte Passwörter aller Pakete)
* - fährt den langlebigen JVM-Daemon herunter (sofern nicht gerade beschäftigt),
* damit die nächste Extraktion mit einem frischen Prozess + frischen Passwörtern
* startet.
*
* Hintergrund: User-Report ein neu hinzugefügtes Passwort griff bei "Jetzt
* entpacken" erst NACH App-Neustart. Die gesamte TS/Java-Kette propagiert die
* Liste pro Request korrekt; die einzige zustandsbehaftete Komponente, die ein
* Neustart zurücksetzt (und dieser Aufruf ebenfalls), ist der Daemon-Prozess.
*
* Bewusst KEIN Shutdown eines beschäftigten Daemons: läuft gerade eine Extraktion
* (z.B. weil Settings während des Entpackens gespeichert werden), bleibt sie
* unangetastet der nächste Lauf bekommt dann ggf. noch den alten Daemon, aber
* der häufige Fall (Liste im Leerlauf ändern) wird sauber abgedeckt.
*/
export function resetExtractorCachesForPasswordChange(): { learnedCleared: number; daemonRestarted: boolean } {
const learnedCleared = packageLearnedPasswords.size;
packageLearnedPasswords.clear();
let daemonRestarted = false;
if (daemonProcess && !daemonBusy) {
shutdownDaemon();
daemonRestarted = true;
}
return { learnedCleared, daemonRestarted };
}
export function archiveFilenamePasswords(archiveName: string): string[] { export function archiveFilenamePasswords(archiveName: string): string[] {
const name = String(archiveName || ""); const name = String(archiveName || "");
if (!name) return []; if (!name) return [];