mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-01 04:47:03 +08:00
fix(plugin): advance runtime generation when a plugin is enabled or disabled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
InfinityPacer
parent
ab729be4ed
commit
40e80f43f5
@@ -83,6 +83,7 @@ class PluginLifecycle:
|
||||
refresh_classification: Callable[[str, Any], None] | None = None,
|
||||
remove_classification: Callable[[str], None] | None = None,
|
||||
gil_fallback_recorder: GilFallbackRecorder = ignore_gil_fallback,
|
||||
enabled_state_recorder: Callable[[str], None] | None = None,
|
||||
) -> None:
|
||||
"""保存注册表、加载器、数据库和事件端口。"""
|
||||
self._classes = classes
|
||||
@@ -107,6 +108,10 @@ class PluginLifecycle:
|
||||
lambda _plugin_id: None
|
||||
)
|
||||
self._gil_fallback_recorder = gil_fallback_recorder
|
||||
# 启用状态变化会改变侧栏、仪表盘和助手形象等前端投影,需要推进前端刷新代次
|
||||
self._enabled_state_recorder = enabled_state_recorder or (
|
||||
lambda _plugin_id: None
|
||||
)
|
||||
self._lifecycle_lock = threading.RLock()
|
||||
self._quiesced_hooks: dict[str, set[str]] = {}
|
||||
|
||||
@@ -239,6 +244,7 @@ class PluginLifecycle:
|
||||
plugin = self._running.get(plugin_id)
|
||||
if not plugin:
|
||||
return
|
||||
was_enabled = bool(plugin.get_state())
|
||||
self._remove_classification(plugin_id)
|
||||
try:
|
||||
with bind_plugin_instance(plugin_id):
|
||||
@@ -254,6 +260,8 @@ class PluginLifecycle:
|
||||
self._remove_classification(plugin_id)
|
||||
raise
|
||||
self._clear_tools()
|
||||
if enabled != was_enabled:
|
||||
self._enabled_state_recorder(plugin_id)
|
||||
|
||||
def _refresh_classification_safely(
|
||||
self,
|
||||
|
||||
@@ -101,6 +101,15 @@ class PluginRegistry:
|
||||
self._gil_fallback_plugins[plugin_id] = True
|
||||
self._generation += 1
|
||||
|
||||
def mark_enabled_state_changed(self, plugin_id: str) -> None:
|
||||
"""记录插件配置切换了启用状态,推进前端刷新代次。
|
||||
|
||||
启停只改实例自身的启用位,不经过运行状态表,但侧栏、仪表盘和助手形象等
|
||||
前端投影都按启用位过滤,读取方需要据此重新拉取。
|
||||
"""
|
||||
del plugin_id
|
||||
self._generation += 1
|
||||
|
||||
def gil_fallback_snapshot(self) -> list[str]:
|
||||
"""返回当前进程内导致 GIL 回退的插件 ID,保持记录顺序。"""
|
||||
return list(self._gil_fallback_plugins)
|
||||
|
||||
@@ -244,6 +244,7 @@ def build_plugin_runtime(
|
||||
refresh_classification=refresh_classification,
|
||||
remove_classification=classification.remove,
|
||||
gil_fallback_recorder=registry.mark_gil_fallback,
|
||||
enabled_state_recorder=registry.mark_enabled_state_changed,
|
||||
)
|
||||
metadata = PluginMetadataMapper(
|
||||
plugin_instance=registry.instance,
|
||||
|
||||
@@ -366,3 +366,15 @@ def test_user_config_endpoint_reports_oversized_agent_pet_state(monkeypatch):
|
||||
assert "16384" in rejected.message
|
||||
assert accepted.success is True
|
||||
assert repository.writes == [("alice", "AgentPet", "builtin")]
|
||||
|
||||
|
||||
def test_registry_advances_generation_on_enabled_state_change():
|
||||
"""启停切换推进前端刷新代次,前端据此重新读取形象声明。"""
|
||||
from app.runtime.extensions.plugin.registry import PluginRegistry
|
||||
|
||||
registry = PluginRegistry()
|
||||
before = registry.generation
|
||||
|
||||
registry.mark_enabled_state_changed("PetDemo")
|
||||
|
||||
assert registry.generation == before + 1
|
||||
|
||||
@@ -30,7 +30,7 @@ from app.scheduler.reconcile import SchedulerReconcileOwner
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _lifecycle(*, plugins, running=None):
|
||||
def _lifecycle(*, plugins, running=None, enabled_state_recorder=None):
|
||||
"""构造隔离外部事件和模块清理的生命周期实例。"""
|
||||
classes: dict = {}
|
||||
running = running if running is not None else {}
|
||||
@@ -50,6 +50,7 @@ def _lifecycle(*, plugins, running=None):
|
||||
database=lambda: PluginDatabase(),
|
||||
log=MagicMock(),
|
||||
event_sender=MagicMock(),
|
||||
enabled_state_recorder=enabled_state_recorder,
|
||||
)
|
||||
return lifecycle, classes, running
|
||||
|
||||
@@ -334,3 +335,31 @@ def test_projection_api_endpoint_binds_owning_instance_without_any_caller_contex
|
||||
|
||||
assert plugin.seen == ["DemoPluginWork"]
|
||||
assert current_plugin_instance_id() is None
|
||||
|
||||
|
||||
def test_lifecycle_initialize_records_enabled_state_change():
|
||||
"""配置切换启用状态时通知注册表推进前端刷新代次,状态不变时不打扰读取方。"""
|
||||
recorded: list[str] = []
|
||||
|
||||
class _Plugin:
|
||||
enabled = True
|
||||
|
||||
def init_plugin(self, config: dict) -> None:
|
||||
self.enabled = bool(config.get("enabled"))
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self.enabled
|
||||
|
||||
plugin = _Plugin()
|
||||
lifecycle, _classes, _running = _lifecycle(
|
||||
plugins=[],
|
||||
running={"DemoPluginWork": plugin},
|
||||
enabled_state_recorder=recorded.append,
|
||||
)
|
||||
|
||||
lifecycle.initialize("DemoPluginWork", {"enabled": True})
|
||||
assert recorded == []
|
||||
|
||||
lifecycle.initialize("DemoPluginWork", {"enabled": False})
|
||||
lifecycle.initialize("DemoPluginWork", {"enabled": True})
|
||||
assert recorded == ["DemoPluginWork", "DemoPluginWork"]
|
||||
|
||||
Reference in New Issue
Block a user