diff --git a/custom_components/audi_dashboard/aktualisierung.py b/custom_components/audi_dashboard/aktualisierung.py index c4c927a..005d504 100644 --- a/custom_components/audi_dashboard/aktualisierung.py +++ b/custom_components/audi_dashboard/aktualisierung.py @@ -217,6 +217,37 @@ def version_von_platte() -> str | None: return None +# Endungen, bei denen Zeilenenden nichts bedeuten. +# +# Am 05.09.2026 am echten Update-Weg gemessen: die installierte Fassung lag mit +# CRLF auf der Platte (Windows-Arbeitsstand), das Gitea-Archiv liefert LF - +# geokodierung.py, screening.py und veroeffentlichung.py galten dadurch als +# geaendert, obwohl sie nach Entfernen der CR byteweise identisch waren. Die +# Erkennung meldete "Neustart noetig" fuer einen rein kosmetischen Unterschied. +# +# Binaerdateien (Schriften, Bilder, das OTA-Zip) bleiben bewusst aussen vor: +# dort waere 0x0D0A echter Inhalt, und ein normalisierter Vergleich koennte +# einen echten Unterschied verschlucken. +TEXTENDUNGEN = (".py", ".json", ".yaml", ".yml", ".md", ".txt", ".js", ".css", ".html", ".svg") + + +def _dateihash(pfad: str) -> str: + """sha256 einer Datei - bei Textdateien ueber auf LF normalisierten Inhalt.""" + hasher = hashlib.sha256() + if pfad.lower().endswith(TEXTENDUNGEN): + try: + with open(pfad, "rb") as datei: + roh = datei.read() + hasher.update(roh.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) + return hasher.hexdigest() + except OSError: + raise + with open(pfad, "rb") as datei: + for block in iter(lambda: datei.read(65536), b""): + hasher.update(block) + return hasher.hexdigest() + + def _dateihashes(ordner: str) -> dict[str, str]: """Alle Dateien unter `ordner` als {relativer Pfad: sha256}. @@ -228,11 +259,7 @@ def _dateihashes(ordner: str) -> dict[str, str]: for name in dateien: pfad = os.path.join(wurzel, name) rel = os.path.relpath(pfad, ordner).replace(os.sep, "/") - hasher = hashlib.sha256() - with open(pfad, "rb") as datei: - for block in iter(lambda: datei.read(65536), b""): - hasher.update(block) - aus[rel] = hasher.hexdigest() + aus[rel] = _dateihash(pfad) return aus diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index b4d4022..58601ea 100644 --- a/custom_components/audi_dashboard/manifest.json +++ b/custom_components/audi_dashboard/manifest.json @@ -1,7 +1,7 @@ { "domain": "audi_dashboard", "name": "Audi Dashboard", - "version": "2026.9.5.24", + "version": "2026.9.5.25", "documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md", "issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues", "codeowners": [ diff --git a/tests/aktualisierung/test_aktualisierung.py b/tests/aktualisierung/test_aktualisierung.py index 1ce93aa..bdff593 100644 --- a/tests/aktualisierung/test_aktualisierung.py +++ b/tests/aktualisierung/test_aktualisierung.py @@ -253,6 +253,34 @@ class NeustartNoetig(unittest.TestCase): self._schreiben(self.neu, "__pycache__/x.cpython-312.pyc", "egal") self.assertFalse(a.neustart_noetig(self.alt, self.neu)) + def test_zeilenenden_zaehlen_nicht_als_aenderung(self): + """Am 05.09.2026 am echten Update-Weg gemessen. + + Die installierte Fassung lag mit CRLF auf der Platte, das Gitea-Archiv + liefert LF - drei .py-Dateien galten dadurch als geaendert, obwohl sie + nach Entfernen der CR byteweise identisch waren, und die Erkennung + verlangte einen Neustart fuer einen rein kosmetischen Unterschied.""" + with open(os.path.join(self.alt, "__init__.py"), "w", newline="\r\n", encoding="utf-8") as datei: + datei.write("x = 1\ny = 2\n") + with open(os.path.join(self.neu, "__init__.py"), "w", newline="\n", encoding="utf-8") as datei: + datei.write("x = 1\ny = 2\n") + self.assertFalse(a.neustart_noetig(self.alt, self.neu)) + + def test_echte_aenderung_bleibt_erkannt_trotz_zeilenenden(self): + with open(os.path.join(self.alt, "__init__.py"), "w", newline="\r\n", encoding="utf-8") as datei: + datei.write("x = 1\n") + with open(os.path.join(self.neu, "__init__.py"), "w", newline="\n", encoding="utf-8") as datei: + datei.write("x = 2\n") + self.assertTrue(a.neustart_noetig(self.alt, self.neu)) + + def test_binaerdatei_wird_nicht_normalisiert(self): + # Bei einer Schriftdatei ist 0x0D0A echter Inhalt - hier darf nicht + # normalisiert werden, sonst verschwaende ein echter Unterschied. + for ordner, inhalt in ((self.alt, b"\x00\r\n\x01"), (self.neu, b"\x00\n\x01")): + with open(os.path.join(ordner, "schrift.woff2"), "wb") as datei: + datei.write(inhalt) + self.assertTrue(a.neustart_noetig(self.alt, self.neu)) + def test_unlesbarer_ordner_gilt_als_neustart(self): self.assertTrue(a.neustart_noetig(os.path.join(self.basis, "gibtsnicht"), self.neu))