MoviePilot/app/core/plugin_manager.py
2023-06-08 13:18:26 +08:00

290 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import traceback
from threading import Thread
from typing import Tuple, Optional, List, Any
from app.core import EventManager
from app.db.systemconfigs import SystemConfigs
from app.helper import ModuleHelper
from app.log import logger
from app.utils.singleton import Singleton
class PluginManager(metaclass=Singleton):
"""
插件管理器
"""
systemconfigs: SystemConfigs = None
eventmanager: EventManager = None
# 插件列表
_plugins: dict = {}
# 运行态插件列表
_running_plugins: dict = {}
# 配置Key
_config_key: str = "plugin.%s"
# 事件处理线程
_thread: Thread = None
# 开关
_active: bool = False
def __init__(self):
self.init_config()
def init_config(self):
self.systemconfigs = SystemConfigs()
self.eventmanager = EventManager()
# 停止已有插件
self.stop()
# 启动插件
self.start()
def __run(self):
"""
事件处理线程
"""
while self._active:
event, handlers = self.eventmanager.get_event()
if event:
logger.info(f"处理事件:{event.event_type} - {handlers}")
for handler in handlers:
try:
names = handler.__qualname__.split(".")
self.run_plugin_method(names[0], names[1], event)
except Exception as e:
logger.error(f"事件处理出错:{str(e)} - {traceback.format_exc()}")
def start(self):
"""
启动
"""
# 加载插件
self.__load_plugins()
# 将事件管理器设为启动
self._active = True
self._thread = Thread(target=self.__run)
# 启动事件处理线程
self._thread.start()
def stop(self):
"""
停止
"""
# 将事件管理器设为停止
self._active = False
# 等待事件处理线程退出
if self._thread:
self._thread.join()
# 停止所有插件
self.__stop_plugins()
def __load_plugins(self):
"""
加载所有插件
"""
# 扫描插件目录
plugins = ModuleHelper.load(
"app.plugins",
filter_func=lambda _, obj: hasattr(obj, 'init_plugin')
)
# 排序
plugins.sort(key=lambda x: x.plugin_order if hasattr(x, "plugin_order") else 0)
self._running_plugins = {}
self._plugins = {}
for plugin in plugins:
plugin_id = plugin.__name__
self._plugins[plugin_id] = plugin
# 生成实例
self._running_plugins[plugin_id] = plugin()
# 初始化配置
self.reload_plugin(plugin_id)
logger.info(f"Plugin Loaded{plugin.__name__}")
def reload_plugin(self, pid: str):
"""
生效插件配置
"""
if not pid:
return
if not self._running_plugins.get(pid):
return
if hasattr(self._running_plugins[pid], "init_plugin"):
try:
self._running_plugins[pid].init_plugin(self.get_plugin_config(pid))
logger.debug(f"生效插件配置:{pid}")
except Exception as err:
logger.error(f"加载插件 {pid} 出错:{err} - {traceback.format_exc()}")
def __stop_plugins(self):
"""
停止所有插件
"""
for plugin in self._running_plugins.values():
if hasattr(plugin, "stop"):
plugin.stop()
def get_plugin_config(self, pid: str) -> dict:
"""
获取插件配置
"""
if not self._plugins.get(pid):
return {}
return self.systemconfigs.get(self._config_key % pid) or {}
def get_plugin_page(self, pid: str) -> Tuple[Optional[str], Optional[str], Optional[str]]:
"""
获取插件额外页面数据
:return: 标题,页面内容,确定按钮响应函数
"""
if not self._running_plugins.get(pid):
return None, None, None
if not hasattr(self._running_plugins[pid], "get_page"):
return None, None, None
return self._running_plugins[pid].get_page()
def get_plugin_script(self, pid: str) -> Optional[str]:
"""
获取插件额外脚本
"""
if not self._running_plugins.get(pid):
return None
if not hasattr(self._running_plugins[pid], "get_script"):
return None
return self._running_plugins[pid].get_script()
def get_plugin_state(self, pid: str) -> Optional[bool]:
"""
获取插件状态
"""
if not self._running_plugins.get(pid):
return None
if not hasattr(self._running_plugins[pid], "get_state"):
return None
return self._running_plugins[pid].get_state()
def save_plugin_config(self, pid: str, conf: dict) -> bool:
"""
保存插件配置
"""
if not self._plugins.get(pid):
return False
return self.systemconfigs.set(self._config_key % pid, conf)
@staticmethod
def __get_plugin_color(plugin: str) -> str:
"""
获取插件的主题色
"""
if hasattr(plugin, "plugin_color") and plugin.plugin_color:
return plugin.plugin_color
return ""
def get_plugins_conf(self, auth_level: int) -> dict:
"""
获取所有插件配置
"""
all_confs = {}
for pid, plugin in self._running_plugins.items():
# 基本属性
conf = {}
# 权限
if hasattr(plugin, "auth_level") \
and plugin.auth_level > auth_level:
continue
# 名称
if hasattr(plugin, "plugin_name"):
conf.update({"name": plugin.plugin_name})
# 描述
if hasattr(plugin, "plugin_desc"):
conf.update({"desc": plugin.plugin_desc})
# 版本号
if hasattr(plugin, "plugin_version"):
conf.update({"version": plugin.plugin_version})
# 图标
if hasattr(plugin, "plugin_icon"):
conf.update({"icon": plugin.plugin_icon})
# ID前缀
if hasattr(plugin, "plugin_config_prefix"):
conf.update({"prefix": plugin.plugin_config_prefix})
# 插件额外的页面
if hasattr(plugin, "get_page"):
title, _, _ = plugin.get_page()
conf.update({"page": title})
# 插件额外的脚本
if hasattr(plugin, "get_script"):
conf.update({"script": plugin.get_script()})
# 主题色
conf.update({"color": self.__get_plugin_color(plugin)})
# 配置项
conf.update({"fields": plugin.get_fields() or {}})
# 配置值
conf.update({"config": self.get_plugin_config(pid)})
# 状态
conf.update({"state": plugin.get_state()})
# 汇总
all_confs[pid] = conf
return all_confs
def get_plugin_apps(self, auth_level: int) -> dict:
"""
获取所有插件
"""
all_confs = {}
for pid, plugin in self._plugins.items():
# 基本属性
conf = {}
# 权限
if hasattr(plugin, "auth_level") \
and plugin.auth_level > auth_level:
continue
# ID
conf.update({"id": pid})
# 名称
if hasattr(plugin, "plugin_name"):
conf.update({"name": plugin.plugin_name})
# 描述
if hasattr(plugin, "plugin_desc"):
conf.update({"desc": plugin.plugin_desc})
# 版本
if hasattr(plugin, "plugin_version"):
conf.update({"version": plugin.plugin_version})
# 图标
if hasattr(plugin, "plugin_icon"):
conf.update({"icon": plugin.plugin_icon})
# 主题色
conf.update({"color": self.__get_plugin_color(plugin)})
if hasattr(plugin, "plugin_author"):
conf.update({"author": plugin.plugin_author})
# 作者链接
if hasattr(plugin, "author_url"):
conf.update({"author_url": plugin.author_url})
# 汇总
all_confs[pid] = conf
return all_confs
def get_plugin_commands(self) -> List[dict]:
"""
获取插件命令
[{
"cmd": "/xx",
"event": EventType.xx,
"desc": "xxxx",
"data": {}
}]
"""
ret_commands = []
for _, plugin in self._running_plugins.items():
if hasattr(plugin, "get_command"):
ret_commands.append(plugin.get_command())
return ret_commands
def run_plugin_method(self, pid: str, method: str, *args, **kwargs) -> Any:
"""
运行插件方法
"""
if not self._running_plugins.get(pid):
return None
if not hasattr(self._running_plugins[pid], method):
return None
return getattr(self._running_plugins[pid], method)(*args, **kwargs)