mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
refactor: delete dead Python code in the core (and stop storing Wi-Fi passwords) (#608)
* refactor(plugins): remove the no-op PluginHealthMonitor Its monitor loop did nothing (`if callbacks: pass`), register_health_check had no callers and api_v3.health_monitor was never read by any route. The live health data comes from PluginHealthTracker, which is untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(store): drop the never-set uninstall tombstones Nothing in production called mark_recently_uninstalled, so the reconciler's was_recently_uninstalled check was always False. The persistent uninstall registry is what actually stops resurrection; the reconciler test now exercises that gate instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(common): delete unused config/display/game helpers, utils and error_handler Nothing in core, the web UI, scripts or the plugin monorepo imports config_helper, display_helper, game_helper, utils or error_handler; only their own tests did. The error_handler re-exports leave src.common's __all__; APIHelper, TextHelper, ScrollHelper, LogoHelper and the adaptive layout exports are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(config): drop ConfigService's unused versioning and save API ConfigVersion, get_version/get_version_history/get_version_config, rollback, save_config, reload, get_plugin_config and the backward-compat load_config/get_config_path/get_secrets_path had no callers. The display controller only uses get_config, subscribe, unsubscribe and shutdown, plus the file watcher. Change detection now compares against the current checksum instead of the last history entry. The subscriber tests asserted `callback.called or True`; they now reload the way the watcher does and assert the notification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(plugins): drop unread plugin state history and callbacks plugin_state.PluginStateManager kept a bounded per-plugin transition history that only get_state_history (tests only) read; get_state_info reports a separate lifetime count, which stays. set_error_info and record_display had no callers, and set_state_with_error's `error` argument only fed the history. The web-side state_manager.PluginStateManager loses subscribe_to_state_changes, _notify_callbacks, set_plugin_error and get_state_version, none of which had callers; with no subscribers the old-state copy in update_plugin_state went with them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(plugins): remove unused PluginManager methods and attribute guards update_all_plugins was only called by a test (the display loop uses run_scheduled_updates); get_plugin_health_metrics, get_plugin_resource_metrics and get_plugin_state had no callers; and plugin_modules was written but never read. plugin_directories is now initialised in __init__, so the hasattr() guards around it go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(plugins): remove unused executor, loader, store and package helpers - PluginExecutor.execute_safe: no callers. - PluginLoader._parse_semver: only its own tests; compatibility.parse_semver is the live copy and test_compatibility.py already covers it. - PluginStoreManager.get_installed_plugin_info: no callers. - PluginResourceMonitor._local: never read. - src.plugin_system.get_store_manager and __api_version__: no importers in core, scripts or the plugin monorepo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(wifi): stop storing Wi-Fi passwords in wifi_config.json WiFiManager appended every joined network's SSID and password, in plaintext, to saved_networks in config/wifi_config.json, and nothing (web UI, backup restore, scripts) ever read them back: NetworkManager keeps its own credentials. The writes are gone, and loading the config now drops any saved_networks key and rewrites the file, so passwords already on disk are scrubbed. Also removes _check_dnsmasq_conflict (never called) and _detect_trixie, whose result only reached one log line, along with the NM_CONNECTIONS_PATHS constant only it used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(display): remove unreachable and unused DisplayController code - _follower_rebuild_scroll_image: never called. - mode_duration (never read) and last_mode_change (write-only). - The `chosen_cap <= 0` branch: chosen_cap is either the minimum of caps already filtered to > 0 or DEFAULT_DYNAMIC_DURATION_CAP (180). - The `max_duration < min_duration` branch directly after `max_duration = max(min_duration, max_duration)`. - The circuit-breaker branch's `display_result = False` and `manager_to_display = None`: the first is overwritten a few lines later, the second is already None there. - The bool-to-bool conversion of execute_display's result, which is always a bool. - The `loaded_plugins` lookup in _update_modules: PluginManager has no such attribute, so it always fell through to `plugins`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(vegas): remove unused config update, boundary finder and refresh VegasModeConfig.update had no callers outside its own tests (the coordinator rebuilds the config with from_config on a change); geometry.find_item_boundary and StreamManager._refresh_plugin_content had no callers at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(run): drop the debug block that pretended to import the plugin system In debug mode run.py put src/plugin_system itself on sys.path and printed "Plugin system import successful" without importing anything. Nothing imports plugin_system modules by bare name, so the path entry did nothing either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: delete tests that test nothing - test/plugins/test_{basketball_scoreboard,calendar,clock_simple, odds_ticker,soccer_scoreboard,text_display}.py skip everywhere the named plugins are not installed, including CI (LEDMATRIX_PLUGINS_DIR holds only the fixture plugin); test_plugin_matrix.py already covers every discovered plugin. Their PluginTestBase and the fixtures only it used (plugins_dir, mock_display_manager, mock_cache_manager, mock_plugin_manager, base_plugin_config in test/plugins/conftest.py) go with them. - test_plugin_system.py: test_discover_plugins (body was `pass`) and test_dependency_check (a comment), plus the test_plugin_manager fixture only the former requested. - test_display_manager.py: test_draw_image asserted that an image it had just assigned was not None. - test_display_controller.py: the rotation and schedule-override tests re-implemented the run-loop arithmetic inline and asserted on their own result without calling the controller. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: expect one plugin_last_update success stamp after update_all_plugins EveryStampRecordsACompletion required at least two success-path stamps; the second was update_all_plugins, removed as test-only. The worker and synchronous paths share the remaining stamp in _execute_update_now, and the check that every stamp calls _note_update_completed is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -86,15 +86,14 @@ class PluginManager:
|
||||
self._skip_reported: set = set()
|
||||
|
||||
# Lock protecting plugin_last_update from concurrent mutation/iteration.
|
||||
# It's written from run_scheduled_updates()/update_all_plugins() (main
|
||||
# loop) and read/diffed by run_scheduled_updates_with_changes(), which
|
||||
# It's written from run_scheduled_updates() (main loop) and read/diffed by run_scheduled_updates_with_changes(), which
|
||||
# Vegas mode calls from its own background update-tick thread.
|
||||
self._plugin_last_update_lock = threading.RLock()
|
||||
|
||||
# Active plugins
|
||||
self.plugins: Dict[str, Any] = {}
|
||||
self.plugin_manifests: Dict[str, Dict[str, Any]] = {}
|
||||
self.plugin_modules: Dict[str, Any] = {}
|
||||
self.plugin_directories: Dict[str, Path] = {}
|
||||
self.plugin_last_update: Dict[str, float] = {}
|
||||
|
||||
# Cached data-fetch intervals per plugin_id.
|
||||
@@ -263,10 +262,7 @@ class PluginManager:
|
||||
with self._discovery_lock:
|
||||
self.plugin_manifests.clear()
|
||||
self.plugin_manifests.update(new_manifests)
|
||||
if not hasattr(self, 'plugin_directories'):
|
||||
self.plugin_directories = {}
|
||||
else:
|
||||
self.plugin_directories.clear()
|
||||
self.plugin_directories.clear()
|
||||
self.plugin_directories.update(new_directories)
|
||||
|
||||
return plugin_ids
|
||||
@@ -327,11 +323,10 @@ class PluginManager:
|
||||
self.state_manager.set_state(plugin_id, PluginState.LOADED)
|
||||
|
||||
# Find plugin directory using PluginLoader
|
||||
plugin_directories = getattr(self, 'plugin_directories', None)
|
||||
plugin_dir = self.plugin_loader.find_plugin_directory(
|
||||
plugin_id,
|
||||
self.plugins_dir,
|
||||
plugin_directories
|
||||
self.plugin_directories
|
||||
)
|
||||
|
||||
if plugin_dir is None:
|
||||
@@ -341,9 +336,7 @@ class PluginManager:
|
||||
return False
|
||||
|
||||
# Update mapping if found via search
|
||||
if plugin_directories is None or plugin_id not in plugin_directories:
|
||||
if not hasattr(self, 'plugin_directories'):
|
||||
self.plugin_directories = {}
|
||||
if plugin_id not in self.plugin_directories:
|
||||
self.plugin_directories[plugin_id] = plugin_dir
|
||||
|
||||
# Get plugin config
|
||||
@@ -379,7 +372,7 @@ class PluginManager:
|
||||
config = self.prepare_plugin_config(plugin_id, config, schema=schema)
|
||||
|
||||
# Use PluginLoader to load plugin
|
||||
plugin_instance, module = self.plugin_loader.load_plugin(
|
||||
plugin_instance, _module = self.plugin_loader.load_plugin(
|
||||
plugin_id=plugin_id,
|
||||
manifest=manifest,
|
||||
plugin_dir=plugin_dir,
|
||||
@@ -391,9 +384,6 @@ class PluginManager:
|
||||
plugins_dir=self.plugins_dir,
|
||||
)
|
||||
|
||||
# Store module
|
||||
self.plugin_modules[plugin_id] = module
|
||||
|
||||
# Register plugin-shipped fonts with the FontManager (if any).
|
||||
# Plugin manifests can declare a "fonts" block that ships custom
|
||||
# fonts with the plugin; FontManager.register_plugin_fonts handles
|
||||
@@ -633,9 +623,6 @@ class PluginManager:
|
||||
# Delegate sub-module and cached-module cleanup to the loader
|
||||
self.plugin_loader.unregister_plugin_modules(plugin_id)
|
||||
|
||||
# Remove from plugin_modules
|
||||
self.plugin_modules.pop(plugin_id, None)
|
||||
|
||||
# Update state
|
||||
self.state_manager.set_state(plugin_id, PluginState.UNLOADED)
|
||||
self.state_manager.clear_state(plugin_id)
|
||||
@@ -778,7 +765,7 @@ class PluginManager:
|
||||
resolved further: dev plugins are symlinks into ``plugins_dir``.
|
||||
"""
|
||||
with self._discovery_lock:
|
||||
if hasattr(self, 'plugin_directories') and plugin_id in self.plugin_directories:
|
||||
if plugin_id in self.plugin_directories:
|
||||
return str(self.plugin_directories[plugin_id])
|
||||
|
||||
plugin_id = safe_path_component(plugin_id)
|
||||
@@ -967,7 +954,7 @@ class PluginManager:
|
||||
self.logger.warning("Plugin %s update() failed; will retry after interval", plugin_id)
|
||||
with self._plugin_last_update_lock:
|
||||
self.plugin_last_update[plugin_id] = failure_time
|
||||
self.state_manager.set_state_with_error(plugin_id, PluginState.ENABLED, error_info, error=err)
|
||||
self.state_manager.set_state_with_error(plugin_id, PluginState.ENABLED, error_info)
|
||||
if self.health_tracker:
|
||||
self.health_tracker.record_failure(plugin_id, err)
|
||||
|
||||
@@ -1299,97 +1286,3 @@ class PluginManager:
|
||||
done = sorted(self._completed_updates)
|
||||
self._completed_updates.clear()
|
||||
return done
|
||||
|
||||
def update_all_plugins(self) -> None:
|
||||
"""
|
||||
Update all enabled plugins.
|
||||
Calls update() on each enabled plugin using PluginExecutor.
|
||||
"""
|
||||
for plugin_id, plugin_instance in list(self.plugins.items()):
|
||||
if not getattr(plugin_instance, "enabled", True):
|
||||
continue
|
||||
|
||||
if not hasattr(plugin_instance, "update"):
|
||||
continue
|
||||
|
||||
# Eligibility check and the RUNNING transition together, so a
|
||||
# concurrent scheduler cannot claim the same plugin (see
|
||||
# _reserve_for_update).
|
||||
if not self._reserve_for_update(plugin_id):
|
||||
continue
|
||||
|
||||
try:
|
||||
success = self.plugin_executor.execute_update(plugin_instance, plugin_id)
|
||||
if success:
|
||||
with self._plugin_last_update_lock:
|
||||
self.plugin_last_update[plugin_id] = time.time()
|
||||
self._note_update_completed(plugin_id)
|
||||
self.state_manager.record_update(plugin_id)
|
||||
self.state_manager.set_state(plugin_id, PluginState.ENABLED)
|
||||
else:
|
||||
self._record_update_failure(plugin_id)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
self.logger.exception("Error updating plugin %s: %s", plugin_id, exc)
|
||||
self._record_update_failure(plugin_id, exc=exc)
|
||||
|
||||
def get_plugin_health_metrics(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get health metrics for all plugins.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping plugin_id to health metrics
|
||||
"""
|
||||
metrics = {}
|
||||
for plugin_id in self.plugins.keys():
|
||||
plugin_metrics = {}
|
||||
|
||||
# Get state information
|
||||
state_info = self.state_manager.get_state_info(plugin_id)
|
||||
plugin_metrics.update(state_info)
|
||||
|
||||
# Get health tracker metrics if available
|
||||
if self.health_tracker:
|
||||
health_info = self.health_tracker.get_health_summary(plugin_id)
|
||||
plugin_metrics['health'] = health_info
|
||||
else:
|
||||
plugin_metrics['health'] = {'status': 'unknown'}
|
||||
|
||||
metrics[plugin_id] = plugin_metrics
|
||||
return metrics
|
||||
|
||||
def get_plugin_resource_metrics(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get resource usage metrics for all plugins.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping plugin_id to resource metrics
|
||||
"""
|
||||
metrics = {}
|
||||
for plugin_id in self.plugins.keys():
|
||||
plugin_metrics = {}
|
||||
|
||||
# Get state information
|
||||
state_info = self.state_manager.get_state_info(plugin_id)
|
||||
plugin_metrics.update(state_info)
|
||||
|
||||
# Get resource monitor metrics if available
|
||||
if self.resource_monitor:
|
||||
resource_info = self.resource_monitor.get_metrics_summary(plugin_id)
|
||||
plugin_metrics['resources'] = resource_info
|
||||
else:
|
||||
plugin_metrics['resources'] = {'status': 'unknown'}
|
||||
|
||||
metrics[plugin_id] = plugin_metrics
|
||||
return metrics
|
||||
|
||||
def get_plugin_state(self, plugin_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get comprehensive state information for a plugin.
|
||||
|
||||
Args:
|
||||
plugin_id: Plugin identifier
|
||||
|
||||
Returns:
|
||||
Dictionary with state information
|
||||
"""
|
||||
return self.state_manager.get_state_info(plugin_id)
|
||||
|
||||
Reference in New Issue
Block a user