diff --git a/apps/backend/src/routes/scan.ts b/apps/backend/src/routes/scan.ts index f763157..076cf79 100644 --- a/apps/backend/src/routes/scan.ts +++ b/apps/backend/src/routes/scan.ts @@ -334,10 +334,6 @@ export async function scanRoutes(app: FastifyInstance): Promise { 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 { 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 { 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 { 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 { 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", {