mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 22:35:08 +00:00
Stage 2 of the web plugin catalog, after #688. - The display publishes a plugin runtime snapshot (plugin_runtime.py) to the shared cache: per plugin loaded, lifecycle state, a short redacted error summary, the version it loaded and when, plus published_at / stale_after / running. Written on change (throttled to 10 s; the RUNNING/ENABLED flip of an ordinary update is not a change) and once a minute otherwise; cleanup() publishes running: false. - The web reads it back and restores loaded / state / error_info in /api/v3/plugins/installed (plus loaded_version, loaded_at and data.runtime). Only a live snapshot counts; stale, stopped or missing answers null and says which. - data/plugin_state.json is retired: every reader and writer moved to config + disk (desired) or the snapshot (observed). Nothing in it was non-derivable, so nothing is migrated and an existing file is left unread. The web-side PluginStateManager (state_manager.py) is removed; the display's plugin_state.PluginStateManager is the only state machine. - StateReconciliation compares config + disk with the snapshot, reporting enabled-but-not-loaded and older-version-loaded as no_action findings. - Backups list installed manifests with enabled from config.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
26 lines
1.1 KiB
Python
26 lines
1.1 KiB
Python
"""The operation history file is replaced atomically.
|
|
|
|
It was written with a plain ``open(path, 'w')`` + ``json.dump`` outside its
|
|
lock. The open truncates first, so a value json can't encode (or a crash, or
|
|
a second Flask thread saving at the same moment) left a partial file, and the
|
|
next load dropped every saved record. It now serialises first and goes
|
|
through a temp file + rename while holding the lock. (plugin_state.json had
|
|
the same fix; it is retired now -- nothing writes it.)
|
|
"""
|
|
|
|
import json
|
|
|
|
from src.plugin_system.operation_history import OperationHistory
|
|
|
|
|
|
def test_history_file_survives_a_failed_save(tmp_path):
|
|
history_file = tmp_path / "operation_history.json"
|
|
history = OperationHistory(history_file=str(history_file))
|
|
history.record_operation("install", plugin_id="clock")
|
|
before = json.loads(history_file.read_text())
|
|
|
|
history.record_operation("update", plugin_id="clock", details={"bad": object()})
|
|
|
|
assert json.loads(history_file.read_text()) == before
|
|
assert [p.name for p in tmp_path.iterdir()] == ["operation_history.json"]
|