mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-04 23:19:43 +08:00
fix Config reload
This commit is contained in:
@@ -4,115 +4,17 @@ import os
|
||||
import secrets
|
||||
import sys
|
||||
import threading
|
||||
from collections import defaultdict
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type, Callable
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type
|
||||
|
||||
from dotenv import set_key
|
||||
from pydantic import BaseModel, BaseSettings, validator, Field
|
||||
|
||||
from app.log import logger, log_settings, LogConfigModel
|
||||
from app.utils.object import ObjectUtils
|
||||
from app.utils.system import SystemUtils
|
||||
from app.utils.url import UrlUtils
|
||||
|
||||
|
||||
class ConfigChangeType(Enum):
|
||||
"""
|
||||
配置变更类型
|
||||
"""
|
||||
ADD = "add"
|
||||
UPDATE = "update"
|
||||
DELETE = "delete"
|
||||
|
||||
|
||||
class ConfigChangeEvent:
|
||||
"""
|
||||
配置变更事件
|
||||
"""
|
||||
|
||||
def __init__(self, key: str, old_value: Any, new_value: Any,
|
||||
change_type: ConfigChangeType = ConfigChangeType.UPDATE):
|
||||
self.key = key
|
||||
self.old_value = old_value
|
||||
self.new_value = new_value
|
||||
self.change_type = change_type
|
||||
self.timestamp = threading.Event()
|
||||
|
||||
|
||||
class ConfigObserver:
|
||||
"""
|
||||
配置观察者接口
|
||||
"""
|
||||
|
||||
def on_config_changed(self, event: ConfigChangeEvent):
|
||||
"""
|
||||
配置变更回调
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ConfigNotifier:
|
||||
"""
|
||||
配置变更通知器
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._observers: Dict[str, List[ConfigObserver]] = defaultdict(list)
|
||||
self._global_observers: List[ConfigObserver] = []
|
||||
self._lock = threading.RLock()
|
||||
|
||||
def add_observer(self, observer: ConfigObserver, config_keys: Optional[List[str]] = None):
|
||||
"""
|
||||
添加观察者
|
||||
:param observer: 观察者对象
|
||||
:param config_keys: 监听的配置键列表,为None时监听所有配置变更
|
||||
"""
|
||||
with self._lock:
|
||||
if config_keys is None:
|
||||
self._global_observers.append(observer)
|
||||
else:
|
||||
for key in config_keys:
|
||||
self._observers[key].append(observer)
|
||||
|
||||
def remove_observer(self, observer: ConfigObserver, config_keys: Optional[List[str]] = None):
|
||||
"""
|
||||
移除观察者
|
||||
:param observer: 观察者对象
|
||||
:param config_keys: 监听的配置键列表,为None时移除全局观察者
|
||||
"""
|
||||
with self._lock:
|
||||
if config_keys is None:
|
||||
if observer in self._global_observers:
|
||||
self._global_observers.remove(observer)
|
||||
else:
|
||||
for key in config_keys:
|
||||
if observer in self._observers[key]:
|
||||
self._observers[key].remove(observer)
|
||||
|
||||
def notify(self, event: ConfigChangeEvent):
|
||||
"""
|
||||
通知观察者配置变更
|
||||
"""
|
||||
with self._lock:
|
||||
# 通知全局观察者
|
||||
for observer in self._global_observers:
|
||||
try:
|
||||
observer.on_config_changed(event)
|
||||
except Exception as e:
|
||||
logger.error(f"配置观察者 {observer} 处理配置变更时出错: {e}")
|
||||
|
||||
# 通知特定配置键的观察者
|
||||
for observer in self._observers.get(event.key, []):
|
||||
try:
|
||||
observer.on_config_changed(event)
|
||||
except Exception as e:
|
||||
logger.error(f"配置观察者 {observer} 处理配置变更 {event.key} 时出错: {e}")
|
||||
|
||||
|
||||
# 全局配置通知器
|
||||
config_notifier = ConfigNotifier()
|
||||
from app.schemas.types import EventType
|
||||
from app.schemas import ConfigChangeEventData
|
||||
|
||||
|
||||
class ConfigModel(BaseModel):
|
||||
@@ -558,8 +460,13 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
||||
if hasattr(log_settings, key):
|
||||
setattr(log_settings, key, converted_value)
|
||||
# 发送配置变更通知
|
||||
event = ConfigChangeEvent(key, old_value, converted_value)
|
||||
config_notifier.notify(event)
|
||||
from app.core.event import eventmanager
|
||||
eventmanager.send_event(etype=EventType.ConfigChanged, data=ConfigChangeEventData(
|
||||
key=key,
|
||||
old_value=old_value,
|
||||
new_value=converted_value,
|
||||
change_type="update"
|
||||
))
|
||||
|
||||
return success, message
|
||||
return True, ""
|
||||
@@ -791,91 +698,3 @@ class GlobalVar(object):
|
||||
|
||||
# 全局标识
|
||||
global_vars = GlobalVar()
|
||||
|
||||
|
||||
class HotReloadManager(ConfigObserver):
|
||||
"""
|
||||
配置热更新管理器
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._reload_handlers: Dict[str, Callable] = {}
|
||||
# 注册为全局配置观察者
|
||||
config_notifier.add_observer(self)
|
||||
|
||||
def register_handler(self, config_keys: List[str], handler: Callable[[Any, Any], None]):
|
||||
"""
|
||||
注册配置变更处理器
|
||||
:param config_keys: 配置键列表
|
||||
:param handler: 处理函数,接收 (old_value, new_value) 参数
|
||||
"""
|
||||
for key in config_keys:
|
||||
self._reload_handlers[key] = handler
|
||||
|
||||
@staticmethod
|
||||
def __get_callable(name: str):
|
||||
"""
|
||||
根据类名获取类实例,首先检查全局变量中是否存在该类,如果不存在则尝试动态导入模块。
|
||||
:param name: 方法名/类名.方法名
|
||||
:return: 类的实例
|
||||
"""
|
||||
# 检查类是否在全局变量中
|
||||
if name in globals():
|
||||
try:
|
||||
class_obj = globals()[name]()
|
||||
return class_obj
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return None
|
||||
|
||||
# TODO 如果类不在全局变量中,尝试动态导入模块并创建实例
|
||||
|
||||
return None
|
||||
|
||||
def on_config_changed(self, event: ConfigChangeEvent):
|
||||
"""
|
||||
处理配置变更事件
|
||||
"""
|
||||
if event.key in self._reload_handlers:
|
||||
try:
|
||||
handler = self._reload_handlers[event.key]
|
||||
# 可执行函数
|
||||
func = self.__get_callable(handler.__qualname__)
|
||||
# 参数数量
|
||||
args_num = ObjectUtils.arguments(func)
|
||||
if args_num < 2:
|
||||
func()
|
||||
else:
|
||||
func(event.old_value, event.new_value)
|
||||
logger.info(f"配置 {event.key} 热更新成功:{func}")
|
||||
except Exception as e:
|
||||
logger.error(f"配置 {event.key} 热更新失败: {e}")
|
||||
|
||||
|
||||
# 初始化热更新管理器
|
||||
hot_reload_manager = HotReloadManager()
|
||||
|
||||
|
||||
def on_config_change(config_keys: List[str]):
|
||||
"""
|
||||
装饰器:用于注册配置变更处理函数
|
||||
|
||||
使用示例:
|
||||
@on_config_change(['PROXY_HOST', 'TMDB_API_KEY'])
|
||||
def handle_config_change(old_value, new_value):
|
||||
pass
|
||||
"""
|
||||
|
||||
def decorator(func: Callable[[Any, Any], None]):
|
||||
hot_reload_manager.register_handler(config_keys, func)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@on_config_change(['DEBUG', 'LOG_LEVEL'])
|
||||
def handle_logger_change():
|
||||
"""
|
||||
默认的配置变更处理函数
|
||||
"""
|
||||
logger.update_loggers()
|
||||
|
||||
@@ -10,7 +10,6 @@ from functools import lru_cache
|
||||
from queue import Empty, PriorityQueue
|
||||
from typing import Callable, Dict, List, Optional, Union
|
||||
|
||||
from app.helper.message import MessageHelper
|
||||
from app.helper.thread import ThreadHelper
|
||||
from app.log import logger
|
||||
from app.schemas import ChainEventData
|
||||
@@ -75,7 +74,6 @@ class EventManager(metaclass=Singleton):
|
||||
__event = threading.Event()
|
||||
|
||||
def __init__(self):
|
||||
self.__messagehelper = MessageHelper()
|
||||
self.__executor = ThreadHelper() # 动态线程池,用于消费事件
|
||||
self.__consumer_threads = [] # 用于保存启动的事件消费者线程
|
||||
self.__event_queue = PriorityQueue() # 优先级队列
|
||||
@@ -140,11 +138,12 @@ class EventManager(metaclass=Singleton):
|
||||
"""
|
||||
event = Event(etype, data, priority)
|
||||
if isinstance(etype, EventType):
|
||||
self.__trigger_broadcast_event(event)
|
||||
return self.__trigger_broadcast_event(event)
|
||||
elif isinstance(etype, ChainEventType):
|
||||
return self.__trigger_chain_event(event)
|
||||
else:
|
||||
logger.error(f"Unknown event type: {etype}")
|
||||
return None
|
||||
|
||||
def add_event_listener(self, event_type: Union[EventType, ChainEventType], handler: Callable,
|
||||
priority: Optional[int] = DEFAULT_EVENT_PRIORITY):
|
||||
@@ -293,7 +292,7 @@ class EventManager(metaclass=Singleton):
|
||||
|
||||
# 对于类实例(实现了 __call__ 方法)
|
||||
if not inspect.isfunction(handler) and hasattr(handler, "__call__"):
|
||||
handler_cls = handler.__class__ # noqa
|
||||
handler_cls = handler.__class__ # noqa
|
||||
return cls.__get_handler_identifier(handler_cls)
|
||||
|
||||
# 对于未绑定方法、静态方法、类方法,使用 __qualname__ 提取类信息
|
||||
@@ -303,6 +302,7 @@ class EventManager(metaclass=Singleton):
|
||||
module = inspect.getmodule(handler)
|
||||
module_name = module.__name__ if module else "unknown_module"
|
||||
return f"{module_name}.{class_name}"
|
||||
return None
|
||||
|
||||
def __is_handler_enabled(self, handler: Callable) -> bool:
|
||||
"""
|
||||
@@ -398,16 +398,28 @@ class EventManager(metaclass=Singleton):
|
||||
|
||||
try:
|
||||
from app.core.plugin import PluginManager
|
||||
from app.core.module import ModuleManager
|
||||
|
||||
if class_name in PluginManager().get_plugin_ids():
|
||||
# 定义一个插件调用函数
|
||||
def plugin_callable():
|
||||
"""
|
||||
插件调用函数
|
||||
"""
|
||||
PluginManager().run_plugin_method(class_name, method_name, event_to_process)
|
||||
|
||||
if is_broadcast_event:
|
||||
self.__executor.submit(plugin_callable)
|
||||
else:
|
||||
plugin_callable()
|
||||
elif class_name in ModuleManager().get_module_ids():
|
||||
module = ModuleManager().get_running_module(class_name)
|
||||
if module:
|
||||
method = getattr(module, method_name, None)
|
||||
if method:
|
||||
if is_broadcast_event:
|
||||
self.__executor.submit(method, event_to_process)
|
||||
else:
|
||||
method(event_to_process)
|
||||
else:
|
||||
# 获取全局对象或模块类的实例
|
||||
class_obj = self.__get_class_instance(class_name)
|
||||
@@ -441,11 +453,20 @@ class EventManager(metaclass=Singleton):
|
||||
if class_name == "Command":
|
||||
module_name = "app.command"
|
||||
module = importlib.import_module(module_name)
|
||||
elif class_name == "Monitor":
|
||||
module_name = "app.monitor"
|
||||
module = importlib.import_module(module_name)
|
||||
elif class_name == "Scheduler":
|
||||
module_name = "app.scheduler"
|
||||
module = importlib.import_module(module_name)
|
||||
elif class_name == "PluginManager":
|
||||
module_name = "app.core.plugin"
|
||||
module = importlib.import_module(module_name)
|
||||
elif class_name.endswith("Chain"):
|
||||
module_name = f"app.chain.{class_name[:-5].lower()}"
|
||||
module = importlib.import_module(module_name)
|
||||
else:
|
||||
logger.debug(f"事件处理出错:无效的 Chain 类名: {class_name},类名必须以 'Chain' 结尾")
|
||||
logger.debug(f"事件处理出错:不支持的类名: {class_name}")
|
||||
return None
|
||||
if hasattr(module, class_name):
|
||||
class_obj = getattr(module, class_name)()
|
||||
@@ -491,9 +512,11 @@ class EventManager(metaclass=Singleton):
|
||||
names = handler.__qualname__.split(".")
|
||||
class_name, method_name = names[0], names[1]
|
||||
|
||||
self.__messagehelper.put(title=f"{event.event_type} 事件处理出错",
|
||||
message=f"{class_name}.{method_name}:{str(e)}",
|
||||
role="system")
|
||||
# 发送系统错误通知
|
||||
from app.helper.message import MessageHelper
|
||||
MessageHelper().put(title=f"{event.event_type} 事件处理出错",
|
||||
message=f"{class_name}.{method_name}:{str(e)}",
|
||||
role="system")
|
||||
self.send_event(
|
||||
EventType.SystemError,
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import traceback
|
||||
from typing import Generator, Optional, Tuple, Any, Union
|
||||
from typing import Generator, Optional, Tuple, Any, Union, List
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.event import eventmanager
|
||||
@@ -164,3 +164,9 @@ class ModuleManager(metaclass=Singleton):
|
||||
获取模块列表
|
||||
"""
|
||||
return self._modules
|
||||
|
||||
def get_module_ids(self) -> List[str]:
|
||||
"""
|
||||
获取模块id列表
|
||||
"""
|
||||
return list(self._modules.keys())
|
||||
|
||||
@@ -15,8 +15,8 @@ from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from app import schemas
|
||||
from app.core.config import settings, on_config_change
|
||||
from app.core.event import eventmanager
|
||||
from app.core.config import settings
|
||||
from app.core.event import eventmanager, Event
|
||||
from app.db.plugindata_oper import PluginDataOper
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
from app.helper.module import ModuleHelper
|
||||
@@ -241,11 +241,17 @@ class PluginManager(metaclass=Singleton):
|
||||
"""
|
||||
return self._plugins
|
||||
|
||||
@on_config_change(['PLUGIN_AUTO_RELOAD', 'DEV'])
|
||||
def handle_config_change(self):
|
||||
@eventmanager.register(EventType.ConfigChanged)
|
||||
def handle_config_changed(self, event: Event):
|
||||
"""
|
||||
处理配置变更事件,重新加载插件监测
|
||||
处理配置变更事件
|
||||
:param event: 事件对象
|
||||
"""
|
||||
if not event:
|
||||
return
|
||||
event_data: schemas.ConfigChangeEventData = event.event_data
|
||||
if event_data.key not in ['DEV', 'PLUGIN_AUTO_RELOAD']:
|
||||
return
|
||||
self.reload_monitor()
|
||||
|
||||
def reload_monitor(self):
|
||||
|
||||
Reference in New Issue
Block a user