mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
refactor: 收口 V3 分层架构与插件兼容边界
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
"""插件源码发现、导入和模块缓存清理。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import sys
|
||||
import traceback
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
PluginImportPreparer = Callable[..., None]
|
||||
PluginImportScanner = Callable[..., None]
|
||||
PluginValidator = Callable[[Any], bool]
|
||||
|
||||
|
||||
class PluginLoader:
|
||||
"""只负责从运行目录发现插件类,并维护对应模块缓存。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
plugins_root: Path,
|
||||
import_preparer: PluginImportPreparer,
|
||||
import_scanner: PluginImportScanner,
|
||||
log: Any,
|
||||
) -> None:
|
||||
"""保存插件目录、导入前置能力和日志端口。"""
|
||||
self._plugins_root = plugins_root
|
||||
self._import_preparer = import_preparer
|
||||
self._import_scanner = import_scanner
|
||||
self._logger = log
|
||||
|
||||
def load(
|
||||
self,
|
||||
plugin_id: Optional[str],
|
||||
installed_plugins: list[str],
|
||||
validator: PluginValidator,
|
||||
) -> list[Any]:
|
||||
"""只导入指定插件或已安装插件,并返回通过契约检查的插件类。"""
|
||||
if not self._plugins_root.exists():
|
||||
self._logger.warning(f"插件目录不存在:{self._plugins_root}")
|
||||
return []
|
||||
|
||||
targets = (
|
||||
[plugin_id.lower()]
|
||||
if plugin_id
|
||||
else [item.lower() for item in installed_plugins]
|
||||
)
|
||||
if not targets:
|
||||
self._logger.debug("没有需要加载的插件")
|
||||
return []
|
||||
|
||||
plugins = []
|
||||
loaded_classes = set()
|
||||
for plugin_dir in self._plugins_root.iterdir():
|
||||
if not plugin_dir.is_dir() or plugin_dir.name.startswith("_"):
|
||||
continue
|
||||
if plugin_dir.name not in targets:
|
||||
self._logger.debug(
|
||||
f"跳过插件目录:{plugin_dir.name}(不在加载列表中)"
|
||||
)
|
||||
continue
|
||||
if not (plugin_dir / "__init__.py").exists():
|
||||
self._logger.debug(
|
||||
f"跳过插件目录:{plugin_dir.name}(缺少__init__.py)"
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
module_name = f"app.plugins.{plugin_dir.name}"
|
||||
self._logger.debug(f"正在导入插件模块:{module_name}")
|
||||
self._import_preparer(
|
||||
plugin_id=plugin_dir.name,
|
||||
plugin_dir=plugin_dir,
|
||||
)
|
||||
self._import_scanner(
|
||||
plugin_id=plugin_dir.name,
|
||||
plugin_dir=plugin_dir,
|
||||
)
|
||||
module = importlib.import_module(module_name)
|
||||
for name, candidate in module.__dict__.items():
|
||||
if name.startswith("_") or not isinstance(candidate, type):
|
||||
continue
|
||||
if name in loaded_classes or not validator(candidate):
|
||||
continue
|
||||
loaded_classes.add(name)
|
||||
plugins.append(candidate)
|
||||
self._logger.debug(f"找到符合条件的插件类:{name}")
|
||||
break
|
||||
except Exception as err:
|
||||
self._logger.error(
|
||||
f"加载插件 {plugin_dir.name} 失败:{str(err)} - "
|
||||
f"{traceback.format_exc()}"
|
||||
)
|
||||
return plugins
|
||||
|
||||
def clear_modules(self, plugin_id: Optional[str] = None) -> list[str]:
|
||||
"""清除指定插件或全部插件的 Python 模块缓存。"""
|
||||
prefix = (
|
||||
f"app.plugins.{plugin_id.lower()}"
|
||||
if plugin_id
|
||||
else "app.plugins"
|
||||
)
|
||||
removed = [
|
||||
module_name
|
||||
for module_name in list(sys.modules)
|
||||
if module_name == prefix or module_name.startswith(f"{prefix}.")
|
||||
]
|
||||
for module_name in removed:
|
||||
sys.modules.pop(module_name, None)
|
||||
self._logger.debug(f"已清除插件模块缓存:{module_name}")
|
||||
importlib.invalidate_caches()
|
||||
self._logger.debug("已清除查找器的缓存")
|
||||
if plugin_id:
|
||||
if removed:
|
||||
self._logger.info(
|
||||
f"插件 {plugin_id} 共清除 {len(removed)} 个模块缓存:{removed}"
|
||||
)
|
||||
else:
|
||||
self._logger.debug(f"插件 {plugin_id} 没有找到需要清除的模块缓存")
|
||||
return removed
|
||||
Reference in New Issue
Block a user