mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
feat(plugin): add virtual plugin instances
This commit is contained in:
@@ -5,36 +5,38 @@ from __future__ import annotations
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.schemas.plugin import PluginInstance, PluginRuntimeStatus
|
||||
|
||||
|
||||
class PluginCloneService:
|
||||
"""协调插件包复制、安装清单、配置复制和运行态刷新。"""
|
||||
"""创建共享源码的虚拟插件实例并协调失败回滚。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
plugin_class: Callable[[str], Optional[Any]],
|
||||
plugin_exists: Callable[[str], bool],
|
||||
package_clone: Callable[..., tuple[bool, str]],
|
||||
installed_plugins: Callable[[], list[str]],
|
||||
save_installed_plugins: Callable[[list[str]], Any],
|
||||
source_plugin_id: Callable[[str], str],
|
||||
save_instance: Callable[[PluginInstance], Any],
|
||||
delete_instance: Callable[[str], bool],
|
||||
read_config: Callable[[str], dict],
|
||||
save_config: Callable[[str, dict], bool],
|
||||
delete_config: Callable[[str], bool],
|
||||
reload_plugin: Callable[[str], Any],
|
||||
running_plugin: Callable[[str], Optional[Any]],
|
||||
initialize_plugin: Callable[[str, dict], Any],
|
||||
remove_plugin: Callable[[str], Any],
|
||||
log: Any,
|
||||
) -> None:
|
||||
"""保存包、持久化和运行态端口。"""
|
||||
"""保存实例描述、持久化和运行态端口。"""
|
||||
self._plugin_class = plugin_class
|
||||
self._plugin_exists = plugin_exists
|
||||
self._package_clone = package_clone
|
||||
self._installed_plugins = installed_plugins
|
||||
self._save_installed_plugins = save_installed_plugins
|
||||
self._source_plugin_id = source_plugin_id
|
||||
self._save_instance = save_instance
|
||||
self._delete_instance = delete_instance
|
||||
self._read_config = read_config
|
||||
self._save_config = save_config
|
||||
self._delete_config = delete_config
|
||||
self._reload_plugin = reload_plugin
|
||||
self._running_plugin = running_plugin
|
||||
self._initialize_plugin = initialize_plugin
|
||||
self._remove_plugin = remove_plugin
|
||||
self._logger = log
|
||||
|
||||
def clone(
|
||||
@@ -47,11 +49,10 @@ class PluginCloneService:
|
||||
version: Optional[str] = None,
|
||||
icon: Optional[str] = None,
|
||||
) -> tuple[bool, str]:
|
||||
"""创建插件分身并保持原有默认禁用配置语义。"""
|
||||
"""创建虚拟分身,复制隔离配置并保持默认禁用语义。"""
|
||||
if not plugin_id or not suffix:
|
||||
return False, "插件ID和分身后缀不能为空"
|
||||
original_class = self._plugin_class(plugin_id)
|
||||
if original_class is None:
|
||||
if self._plugin_class(plugin_id) is None:
|
||||
return False, f"原插件 {plugin_id} 不存在"
|
||||
|
||||
clone_id = f"{plugin_id}{suffix.lower()}"
|
||||
@@ -59,38 +60,44 @@ class PluginCloneService:
|
||||
return False, f"分身插件 {clone_id} 已存在"
|
||||
|
||||
try:
|
||||
success, message = self._package_clone(
|
||||
plugin_id=plugin_id,
|
||||
clone_id=clone_id,
|
||||
original_class_name=original_class.__name__,
|
||||
suffix=suffix.lower(),
|
||||
name=name,
|
||||
description=description,
|
||||
version=version,
|
||||
icon=icon,
|
||||
instance = PluginInstance(
|
||||
instance_id=clone_id,
|
||||
source_plugin_id=self._source_plugin_id(plugin_id),
|
||||
plugin_name=name or None,
|
||||
plugin_desc=description or None,
|
||||
plugin_icon=icon or None,
|
||||
)
|
||||
if not success:
|
||||
return False, message
|
||||
|
||||
installed = list(self._installed_plugins())
|
||||
if clone_id not in installed:
|
||||
installed.append(clone_id)
|
||||
self._save_installed_plugins(installed)
|
||||
self._save_instance(instance)
|
||||
|
||||
original_config = self._read_config(plugin_id)
|
||||
if original_config:
|
||||
clone_config = dict(original_config)
|
||||
clone_config["enable"] = False
|
||||
clone_config["enabled"] = False
|
||||
self._save_config(clone_id, clone_config)
|
||||
if not self._save_config(clone_id, clone_config):
|
||||
raise RuntimeError("虚拟实例配置保存失败")
|
||||
|
||||
self._reload_plugin(clone_id)
|
||||
clone_instance = self._running_plugin(clone_id)
|
||||
clone_config = self._read_config(clone_id)
|
||||
if clone_instance and clone_config:
|
||||
self._initialize_plugin(clone_id, clone_config)
|
||||
status = self._reload_plugin(clone_id)
|
||||
if status is PluginRuntimeStatus.LOAD_FAILED:
|
||||
raise RuntimeError("虚拟实例加载失败")
|
||||
self._logger.info(f"插件分身 {clone_id} 创建成功")
|
||||
return True, clone_id
|
||||
except Exception as error: # noqa: BLE001
|
||||
self._rollback(clone_id)
|
||||
self._logger.error(f"创建插件分身失败:{error}")
|
||||
return False, f"创建插件分身失败:{error}"
|
||||
|
||||
def _rollback(self, clone_id: str) -> None:
|
||||
"""逐项清理失败实例,单个清理错误不得阻断其余回滚。"""
|
||||
rollback_steps = (
|
||||
("运行态", self._remove_plugin),
|
||||
("实例描述", self._delete_instance),
|
||||
("配置", self._delete_config),
|
||||
)
|
||||
for label, rollback in rollback_steps:
|
||||
try:
|
||||
rollback(clone_id)
|
||||
except Exception as rollback_error: # noqa: BLE001
|
||||
self._logger.warning(
|
||||
f"回滚插件分身 {clone_id} 的{label}失败:{rollback_error}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user