round61: Delta-Vergleich gegen vollstaendige DB-Abfrage statt lueckenhafter Lauf-Teilliste, Formulierung 'geloescht' statt irrefuehrendem 'fehlen jetzt'

This commit is contained in:
2026-07-26 15:34:00 +02:00
parent eef4ddc775
commit fb5874d7d0

View File

@@ -334,10 +334,6 @@ export async function scanRoutes(app: FastifyInstance): Promise<void> {
const allStale: DeviceScanResult["staleServices"] = [];
const allNameChanges: DeviceScanResult["nameChanges"] = [];
const allNew: DeviceScanResult["newServices"] = [];
// Sammelt die IDs ALLER gefundenen Dienste über den kompletten
// Sammel-Scan hinweg - für den Vergleich mit dem vorherigen Lauf (siehe
// weiter unten bei finishJob).
const allServiceIds: string[] = [];
let cancelled = false;
try {
@@ -540,7 +536,6 @@ export async function scanRoutes(app: FastifyInstance): Promise<void> {
allStale.push(...result.staleServices);
allNameChanges.push(...result.nameChanges);
allNew.push(...result.newServices);
allServiceIds.push(...result.services.map((s) => s.id));
return;
}
@@ -571,7 +566,6 @@ export async function scanRoutes(app: FastifyInstance): Promise<void> {
allStale.push(...result.staleServices);
allNameChanges.push(...result.nameChanges);
allNew.push(...result.newServices);
allServiceIds.push(...result.services.map((s) => s.id));
} catch (err) {
// Einzelnes fehlgeschlagenes Gerät soll den Rest nicht
// abbrechen - ABER trotzdem persistent loggen, statt
@@ -604,12 +598,39 @@ export async function scanRoutes(app: FastifyInstance): Promise<void> {
updateJobProgress("devices", scannedCount, null);
}
// NEU (round60): Vergleich mit dem VORHERIGEN Sammel-Scan (siehe
// gleiches Prinzip beim FritzBox-Scan oben) - Mengenvergleich der
// gefundenen Dienst-IDs statt reiner Zahlenvergleich.
// NEU (round60, korrigiert round61): Vergleich mit dem VORHERIGEN
// Sammel-Scan (siehe gleiches Prinzip beim FritzBox-Scan oben) -
// Mengenvergleich der Dienst-IDs statt reiner Zahlenvergleich.
//
// WICHTIG: Als "aktueller Stand" wird eine FRISCHE, VOLLSTÄNDIGE
// Abfrage aller Dienste aus der Datenbank genutzt (serviceRepo.
// listServices()) - NICHT die Liste der in DIESEM Lauf tatsächlich
// erfolgreich gescannten Dienste. Der Unterschied ist entscheidend:
// Geräte, die in diesem Lauf übersprungen wurden (z. B. weil parallel
// schon ein Einzelgeräte-Scan für sie lief, siehe tryLockDevice) oder
// bei denen der Scan mit einem Fehler abgebrochen ist, haben ihre
// bestehenden Dienste NIE in eine nur-aus-diesem-Lauf gesammelte
// Liste geschafft - beim NÄCHSTEN Lauf sahen deren ganz normalen,
// unveränderten Dienste dann fälschlich wie "neu" aus (siehe
// Bugreport: Zusammenfassung zeigte "3 neue Dienste", obwohl nur 1
// Dienst tatsächlich neu angelegt wurde). Eine frische DB-Abfrage hat
// dieses Problem nicht, sie spiegelt immer den WIRKLICHEN
// Gesamtstand wider, unabhängig davon, welche Geräte in diesem
// konkreten Lauf erfolgreich waren.
//
// Das ändert auch die Bedeutung von "nicht mehr da": Diese Dienste
// wurden nicht in diesem Scan einfach nicht gefunden (dafür gibt es
// ja bereits staleServices mit eigener Karenzzeit und eigenem
// Bestätigen/Verwerfen-Dialog weiter unten) - sie wurden
// TATSÄCHLICH AUS DER DATENBANK GELÖSCHT, entweder über genau diesen
// Bestätigen-Dialog oder manuell. Das ist eine reine Feststellung
// ("das ist seit dem letzten Mal passiert"), keine neue
// Entscheidung, die hier nochmal angezeigt/genehmigt werden müsste -
// die Genehmigung ist ja bereits an anderer Stelle erfolgt.
const previousServiceIdsRaw = settingsRepo.getSetting("deviceScanLastServiceIds");
const previousServiceIds: string[] | null = previousServiceIdsRaw ? JSON.parse(previousServiceIdsRaw) : null;
const currentServiceIdSet = new Set(allServiceIds);
const currentServiceIds = serviceRepo.listServices().map((s) => s.id);
const currentServiceIdSet = new Set(currentServiceIds);
let changesSinceLastScan: string;
if (cancelled) {
@@ -618,22 +639,24 @@ export async function scanRoutes(app: FastifyInstance): Promise<void> {
changesSinceLastScan = "Erster Scan - kein Vergleich zu einem vorherigen Lauf möglich.";
} else {
const previousSet = new Set(previousServiceIds);
const newlyAppeared = allServiceIds.filter((id) => !previousSet.has(id));
const noLongerSeen = previousServiceIds.filter((id) => !currentServiceIdSet.has(id));
if (newlyAppeared.length === 0 && noLongerSeen.length === 0) {
const newlyAppeared = currentServiceIds.filter((id) => !previousSet.has(id));
const deletedSinceLastScan = previousServiceIds.filter((id) => !currentServiceIdSet.has(id));
if (newlyAppeared.length === 0 && deletedSinceLastScan.length === 0) {
changesSinceLastScan = "Keine Änderungen seit dem letzten Sammel-Scan.";
} else {
const parts: string[] = [];
if (newlyAppeared.length > 0) parts.push(`${newlyAppeared.length} neue(r) Dienst(e)`);
if (noLongerSeen.length > 0) parts.push(`${noLongerSeen.length} Dienst(e) fehlen jetzt`);
if (deletedSinceLastScan.length > 0)
parts.push(`${deletedSinceLastScan.length} Dienst(e) seither gelöscht`);
changesSinceLastScan = `Änderungen seit dem letzten Sammel-Scan: ${parts.join(", ")}.`;
}
}
// Bei einem abgebrochenen Lauf NICHT als Vergleichsbasis speichern -
// sonst würde ein unvollständiger Durchlauf fälschlich "Dienste
// fehlen jetzt" für alle noch gar nicht gescannten Geräte anzeigen.
// sonst würde ein unvollständiger Durchlauf fälschlich melden, dass
// Dienste "gelöscht" wurden, nur weil noch nicht alle Geräte an der
// Reihe waren.
if (!cancelled) {
settingsRepo.setSetting("deviceScanLastServiceIds", JSON.stringify(allServiceIds));
settingsRepo.setSetting("deviceScanLastServiceIds", JSON.stringify(currentServiceIds));
}
finishJob("devices", {