fix(plugin): reset stopped plugin config and data (#6031)

This commit is contained in:
InfinityPacer
2026-06-30 22:37:07 +08:00
committed by GitHub
parent a0f613fa1e
commit 2764742b86
3 changed files with 47 additions and 12 deletions
+2 -2
View File
@@ -615,9 +615,9 @@ def reset_plugin(
# 事件处理器需要运行中插件完成补偿;补偿后先停止插件,避免删除数据时仍有任务读写旧状态。 # 事件处理器需要运行中插件完成补偿;补偿后先停止插件,避免删除数据时仍有任务读写旧状态。
plugin_manager.stop(plugin_id) plugin_manager.stop(plugin_id)
# 删除配置 # 删除配置
plugin_manager.delete_plugin_config(plugin_id) plugin_manager.delete_plugin_config(plugin_id, force=True)
# 删除插件所有数据 # 删除插件所有数据
plugin_manager.delete_plugin_data(plugin_id) plugin_manager.delete_plugin_data(plugin_id, force=True)
# 重新加载插件 # 重新加载插件
reload_plugin(plugin_id) reload_plugin(plugin_id)
return schemas.Response(success=True) return schemas.Response(success=True)
+6 -4
View File
@@ -723,21 +723,23 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
await SystemConfigOper().async_set(self._config_key % pid, conf) await SystemConfigOper().async_set(self._config_key % pid, conf)
return True return True
def delete_plugin_config(self, pid: str) -> bool: def delete_plugin_config(self, pid: str, force: bool = False) -> bool:
""" """
删除插件配置 删除插件配置
:param pid: 插件ID :param pid: 插件ID
:param force: 插件停止后仍允许按插件 ID 删除持久化配置
""" """
if not self._plugins.get(pid): if not force and not self._plugins.get(pid):
return False return False
return SystemConfigOper().delete(self._config_key % pid) return SystemConfigOper().delete(self._config_key % pid)
def delete_plugin_data(self, pid: str) -> bool: def delete_plugin_data(self, pid: str, force: bool = False) -> bool:
""" """
删除插件数据 删除插件数据
:param pid: 插件ID :param pid: 插件ID
:param force: 插件停止后仍允许按插件 ID 删除持久化数据
""" """
if not self._plugins.get(pid): if not force and not self._plugins.get(pid):
return False return False
PluginDataOper().del_data(pid) PluginDataOper().del_data(pid)
return True return True
+39 -6
View File
@@ -7,8 +7,10 @@ from app.api.endpoints.plugin import plugin_releases
from app.api.endpoints.plugin import reset_plugin from app.api.endpoints.plugin import reset_plugin
from app.api.endpoints.system import sync_plugin_market_from_wiki from app.api.endpoints.system import sync_plugin_market_from_wiki
from app.core.config import settings from app.core.config import settings
from app.core.plugin import PluginManager
from app.schemas.event import PluginDataResetEventData from app.schemas.event import PluginDataResetEventData
from app.schemas.types import ChainEventType from app.schemas.types import ChainEventType
from app.utils.singleton import Singleton
def test_plugin_history_merges_remote_metadata(): def test_plugin_history_merges_remote_metadata():
@@ -279,12 +281,12 @@ def test_reset_plugin_sends_pre_reset_chain_event_before_deleting_data():
plugin_manager = MagicMock() plugin_manager = MagicMock()
calls = [] calls = []
def delete_config(plugin_id): def delete_config(plugin_id, force=False):
calls.append(("delete_config", plugin_id)) calls.append(("delete_config", plugin_id, force))
return True return True
def delete_data(plugin_id): def delete_data(plugin_id, force=False):
calls.append(("delete_data", plugin_id)) calls.append(("delete_data", plugin_id, force))
return True return True
def stop_plugin(plugin_id): def stop_plugin(plugin_id):
@@ -314,7 +316,38 @@ def test_reset_plugin_sends_pre_reset_chain_event_before_deleting_data():
assert event_call[2].reset_data is True assert event_call[2].reset_data is True
assert calls[1:] == [ assert calls[1:] == [
("stop", "SubscribeAssistantEnhanced"), ("stop", "SubscribeAssistantEnhanced"),
("delete_config", "SubscribeAssistantEnhanced"), ("delete_config", "SubscribeAssistantEnhanced", True),
("delete_data", "SubscribeAssistantEnhanced"), ("delete_data", "SubscribeAssistantEnhanced", True),
] ]
reload_plugin_mock.assert_called_once_with("SubscribeAssistantEnhanced") reload_plugin_mock.assert_called_once_with("SubscribeAssistantEnhanced")
def test_delete_plugin_config_can_force_delete_after_plugin_is_stopped():
"""
重置入口会先停止插件;配置删除需要能处理运行态注册已清理的插件 ID。
"""
Singleton._instances.pop((PluginManager, (), frozenset()), None)
manager = PluginManager()
with patch("app.core.plugin.SystemConfigOper") as system_config_oper:
system_config_oper.return_value.delete.return_value = True
assert manager.delete_plugin_config("DemoPlugin", force=True) is True
system_config_oper.return_value.delete.assert_called_once_with("plugin.DemoPlugin")
Singleton._instances.pop((PluginManager, (), frozenset()), None)
def test_delete_plugin_data_can_force_delete_after_plugin_is_stopped():
"""
重置入口会先停止插件;插件数据删除不能依赖运行态注册仍存在。
"""
Singleton._instances.pop((PluginManager, (), frozenset()), None)
manager = PluginManager()
calls = []
with patch("app.core.plugin.PluginDataOper") as plugin_data_oper:
plugin_data_oper.return_value.del_data.side_effect = lambda pid: calls.append(pid)
assert manager.delete_plugin_data("DemoPlugin", force=True) is True
assert calls == ["DemoPlugin"]
Singleton._instances.pop((PluginManager, (), frozenset()), None)