mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +00:00
refactor(plugins): one plugin-directory resolver (#623)
* refactor(plugins): one resolver for plugin id -> directory Five places mapped a plugin id to its directory, each with its own rules and each re-reading manifests per lookup: PluginManager discovery and get_plugin_directory, PluginLoader.find_plugin_directory, PluginStoreManager._find_plugin_path / list_installed_plugins, and state_reconciliation.disk_plugin_ids. They disagreed on backup dirs, on whether the manifest id or the directory name is the id, on duplicate ids and on path safety. src/plugin_system/plugin_dirs.py now holds the rules once: PluginDirectoryIndex scans one directory and reads each manifest once; resolve_plugin_dir() searches directories in order. What legitimately differs per caller is an explicit argument: search dirs (discovery and the loader: configured dir only; the store: configured then sibling plugins/), ledmatrix- prefix (not for the store), case folding (loader only), manifest pass (not for get_plugin_directory, whose discovery map already holds it). Behaviour changes, all for layouts installs do not produce: - a directory whose manifest declares the id beats one merely named for it (discovery already worked this way; the loader and store now agree) - the store searches the configured dir completely before plugins/ - backup and hidden dirs are skipped everywhere (the loader's case and manifest scans and list_installed_plugins used to return them) - duplicate ids resolve deterministically (exact name, then ledmatrix-<id>, then by name) with a one-time warning; discovery no longer lists the id twice - disk_plugin_ids / list_installed_plugins report manifest ids, falling back to the directory name; auto-update looks the directory up - ids that are not one plain path segment resolve to nothing in every caller (the loader used to truncate them, the store to join them) The .standalone-backup- marker is one constant, BACKUP_MARKER, used by store_manager's rename-aside names and every lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): one plugin-directory resolver 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:
@@ -16,9 +16,13 @@ change to the fallback chains is a deliberate one.
|
||||
|
||||
The `.standalone-backup-` contract: store_manager renames a plugin dir aside
|
||||
with that substring during install/rollback; discovery MUST skip such dirs
|
||||
or a half-finished install would surface a ghost plugin. The substring is
|
||||
duplicated as a literal in both files — this test breaks if either side
|
||||
changes it unilaterally.
|
||||
or a half-finished install would surface a ghost plugin. The substring now
|
||||
lives once, as plugin_dirs.BACKUP_MARKER, which both sides import; its value
|
||||
is pinned because debris already on devices carries exactly that text.
|
||||
|
||||
All of them now resolve through src/plugin_system/plugin_dirs.py; the
|
||||
per-caller differences pinned here are explicit arguments there. The rules
|
||||
themselves are covered table-style in test_plugin_dirs.py.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -198,14 +202,16 @@ class TestStandaloneBackupContract:
|
||||
found = _scanner()._scan_directory_for_plugins(plugins_dir)
|
||||
assert found == ["real-plugin"]
|
||||
|
||||
def test_backup_substring_literal_matches_across_files(self):
|
||||
"""The substring is duplicated in plugin_manager (skip check) and
|
||||
store_manager (rename-aside names). If either side changes it, the
|
||||
other silently stops honoring the contract — this test is the
|
||||
tripwire."""
|
||||
def test_backup_marker_is_shared_and_unchanged(self):
|
||||
"""store_manager (rename-aside names) and every lookup (skip check)
|
||||
must agree on the marker. Both now import one constant; the value is
|
||||
pinned because renaming it would make existing debris on devices
|
||||
visible as plugins again."""
|
||||
from src.plugin_system import plugin_dirs
|
||||
assert plugin_dirs.BACKUP_MARKER == '.standalone-backup-'
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
pm_text = (root / "src/plugin_system/plugin_manager.py").read_text()
|
||||
sm_text = (root / "src/plugin_system/store_manager.py").read_text()
|
||||
assert "'.standalone-backup-'" in pm_text.replace('"', "'")
|
||||
assert ".standalone-backup-" in sm_text
|
||||
|
||||
sm_text = (root / "src/plugin_system/store_manager.py").read_text(encoding="utf-8")
|
||||
assert "{BACKUP_MARKER}preinstall" in sm_text
|
||||
assert "{BACKUP_MARKER}migrating" in sm_text
|
||||
assert plugin_dirs.is_ignored_dir_name(
|
||||
"demo" + plugin_dirs.BACKUP_MARKER + "preinstall")
|
||||
|
||||
Reference in New Issue
Block a user