diff --git a/plugins/ledmatrix-flights/manager.py b/plugins/ledmatrix-flights/manager.py index 06385747..8e3a8d88 100644 --- a/plugins/ledmatrix-flights/manager.py +++ b/plugins/ledmatrix-flights/manager.py @@ -2406,17 +2406,23 @@ def _get_map_background(self, center_lat: float, center_lon: float, self.logger.debug(f"[Flight Tracker] Map displays {desired_miles_wide:.1f} miles wide x {desired_miles_high:.1f} miles high (no stretching)") self.logger.debug(f"[Flight Tracker] Native tile scale: {pixels_per_mile_at_zoom:.3f} pixels/mile, cropped {crop_width_needed}x{crop_height_needed} pixels, scaled to {self.display_width}x{self.display_height}") - # Debug: Save composite image to see what's happening - try: - debug_composite = Path("debug_composite.png") - composite.save(debug_composite) - self.logger.debug(f"[Flight Tracker] Saved composite to: {debug_composite}") - - debug_cropped = Path("debug_cropped.png") - cropped.save(debug_cropped) - self.logger.debug(f"[Flight Tracker] Saved cropped to: {debug_cropped}") - except Exception as e: - self.logger.debug(f"[Flight Tracker] Could not save debug images: {e}") + # Dump the composite and the crop for inspection -- but only when debug + # logging is on. These were unconditional, so every map rebuild wrote + # two PNGs (~27KB) into the process's working directory: SD-card wear + # on a device that has nothing to gain from it, and two untracked files + # appearing in the checkout the service runs from. There is no config + # flag for them, so nobody could turn them off either. + if self.logger.isEnabledFor(logging.DEBUG): + try: + debug_composite = Path("debug_composite.png") + composite.save(debug_composite) + self.logger.debug(f"[Flight Tracker] Saved composite to: {debug_composite}") + + debug_cropped = Path("debug_cropped.png") + cropped.save(debug_cropped) + self.logger.debug(f"[Flight Tracker] Saved cropped to: {debug_cropped}") + except Exception as e: + self.logger.debug(f"[Flight Tracker] Could not save debug images: {e}") return cropped diff --git a/plugins/ledmatrix-flights/manifest.json b/plugins/ledmatrix-flights/manifest.json index a293588b..0ab5d40b 100644 --- a/plugins/ledmatrix-flights/manifest.json +++ b/plugins/ledmatrix-flights/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-flights", "name": "Flight Tracker", - "version": "1.12.13", + "version": "1.12.14", "description": "Real-time aircraft tracking with ADS-B/FlightRadar24/OpenSky/adsb.fi/adsb.lol data, map backgrounds, area mode, flight tracking, anchor airport, flight records, and optional airport weather (METAR/TAF/PIREP/SIGMET via the free NOAA Aviation Weather Center API)", "author": "ChuckBuilds", "entry_point": "manager.py", @@ -37,6 +37,12 @@ "min_ledmatrix_version": "2.0.0", "max_ledmatrix_version": "3.0.0", "versions": [ + { + "version": "1.12.14", + "released": "2026-08-22", + "ledmatrix_min_version": "2.0.0", + "notes": "Stop writing debug PNGs on every map rebuild. _get_map_background saved debug_composite.png and debug_cropped.png unconditionally, so each composite cost ~27KB of SD-card writes and dropped two untracked files into whatever directory the service runs from -- for a normal install, the checkout itself. There was no config flag, so they could not be turned off. They are still written when debug logging is on, which is when they are actually wanted." + }, { "version": "1.12.13", "released": "2026-08-19", diff --git a/plugins/ledmatrix-flights/test_no_debug_images_in_normal_operation.py b/plugins/ledmatrix-flights/test_no_debug_images_in_normal_operation.py new file mode 100644 index 00000000..00cf5292 --- /dev/null +++ b/plugins/ledmatrix-flights/test_no_debug_images_in_normal_operation.py @@ -0,0 +1,68 @@ +"""Building a map must not write PNGs into the working directory. + +_get_map_background() saved debug_composite.png and debug_cropped.png on every +composite, unconditionally. On a Pi that is pointless SD-card wear (~27KB a +time), and the files land in whatever directory the service runs from -- for a +normal install, the checkout itself, where they show up as untracked files. +There was no config flag, so they could not be turned off. + +They are useful when you are debugging the map, so they are kept behind the +debug log level rather than deleted. +""" +import ast +import logging +from pathlib import Path + +MANAGER = Path(__file__).resolve().parent / "manager.py" +TREE = ast.parse(MANAGER.read_text(encoding="utf-8")) + + +def _save_calls(): + """(lineno, unparsed) for every .save(...) writing a debug_* path.""" + out = [] + for n in ast.walk(TREE): + if isinstance(n, ast.Call) and isinstance(n.func, ast.Attribute) \ + and n.func.attr == "save": + src = ast.unparse(n) + if "debug_" in src: + out.append((n.lineno, src)) + return out + + +def _enclosing_tests(lineno): + """Source of every `if` test whose body contains this line.""" + parents = {} + for node in ast.walk(TREE): + for child in ast.iter_child_nodes(node): + parents[child] = node + out = [] + for node in ast.walk(TREE): + if isinstance(node, ast.If) and any( + s.lineno <= lineno <= (s.end_lineno or s.lineno) for s in node.body): + out.append(ast.unparse(node.test)) + return out + + +def test_the_debug_saves_still_exist(): + """Pin the premise -- if they are removed entirely this file is obsolete.""" + calls = _save_calls() + assert calls, "no debug image saves found; delete this test if that was deliberate" + + +def test_every_debug_save_is_behind_a_debug_check(): + offenders = [] + for lineno, src in _save_calls(): + guards = " ".join(_enclosing_tests(lineno)) + if "isEnabledFor" not in guards and "DEBUG" not in guards: + offenders.append((lineno, src[:60])) + assert not offenders, ( + f"unconditional debug image write(s) at {offenders} -- these run on " + "every map composite, wearing the SD card and dropping untracked PNGs " + "into the service's working directory") + + +def test_the_guard_uses_the_real_logging_level(): + """isEnabledFor, not a hand-rolled flag that can drift from the log config.""" + src = MANAGER.read_text(encoding="utf-8") + assert "self.logger.isEnabledFor(logging.DEBUG)" in src + assert logging.DEBUG < logging.INFO # sanity: DEBUG really is the quiet level