From bbfd8ca3f501b9ea0ff095e966aa6692a0c85fc5 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 3 Jun 2025 23:08:58 +0800 Subject: [PATCH] fix Config reload --- app/api/endpoints/system.py | 5 + app/core/config.py | 201 ++------------------------- app/core/event.py | 41 ++++-- app/core/module.py | 8 +- app/core/plugin.py | 16 ++- app/db/systemconfig_oper.py | 44 ++++-- app/modules/emby/__init__.py | 25 +++- app/modules/jellyfin/__init__.py | 19 ++- app/modules/plex/__init__.py | 19 ++- app/modules/qbittorrent/__init__.py | 22 ++- app/modules/slack/__init__.py | 20 ++- app/modules/synologychat/__init__.py | 20 ++- app/modules/telegram/__init__.py | 23 ++- app/modules/transmission/__init__.py | 19 ++- app/modules/trimemedia/__init__.py | 19 ++- app/modules/vocechat/__init__.py | 20 ++- app/modules/webpush/__init__.py | 21 ++- app/modules/wechat/__init__.py | 21 ++- app/monitor.py | 20 ++- app/scheduler.py | 23 ++- app/schemas/event.py | 10 ++ app/schemas/types.py | 2 + 22 files changed, 338 insertions(+), 280 deletions(-) diff --git a/app/api/endpoints/system.py b/app/api/endpoints/system.py index 24db7ea8..916ab34f 100644 --- a/app/api/endpoints/system.py +++ b/app/api/endpoints/system.py @@ -37,6 +37,7 @@ from app.utils.crypto import HashUtils from app.utils.http import RequestUtils from app.utils.security import SecurityUtils from app.utils.url import UrlUtils +from core.event import eventmanager from version import APP_VERSION router = APIRouter() @@ -230,6 +231,10 @@ def set_env_setting(env: dict, } ) + if success_updates: + for key, value in success_updates.items(): + eventmanager.send_event() + return schemas.Response( success=True, message="所有配置项更新成功", diff --git a/app/core/config.py b/app/core/config.py index 26f0557e..01034047 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -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() diff --git a/app/core/event.py b/app/core/event.py index 2a4e79d3..a30d00cb 100644 --- a/app/core/event.py +++ b/app/core/event.py @@ -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, { diff --git a/app/core/module.py b/app/core/module.py index 25753d9b..90a906d6 100644 --- a/app/core/module.py +++ b/app/core/module.py @@ -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()) diff --git a/app/core/plugin.py b/app/core/plugin.py index 405bf364..afa18031 100644 --- a/app/core/plugin.py +++ b/app/core/plugin.py @@ -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): diff --git a/app/db/systemconfig_oper.py b/app/db/systemconfig_oper.py index 78c554d9..531b5ac0 100644 --- a/app/db/systemconfig_oper.py +++ b/app/db/systemconfig_oper.py @@ -1,10 +1,10 @@ from typing import Any, Union -from app.core.config import ConfigChangeEvent, config_notifier, ConfigChangeType from app.db import DbOper from app.db.models.systemconfig import SystemConfig -from app.schemas.types import SystemConfigKey +from app.schemas.types import SystemConfigKey, EventType from app.utils.singleton import Singleton +from app.schemas import ConfigChangeEventData class SystemConfigOper(DbOper, metaclass=Singleton): @@ -35,22 +35,34 @@ class SystemConfigOper(DbOper, metaclass=Singleton): conf.update(self._db, {"value": value}) # 发送配置变更通知 if old_value != value: - event = ConfigChangeEvent(key, old_value=old_value, new_value=value, - change_type=ConfigChangeType.UPDATE) - 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=value, + change_type="update" + )) else: conf.delete(self._db, conf.id) # 发送配置删除通知 - event = ConfigChangeEvent(key, old_value=old_value, new_value=None, - change_type=ConfigChangeType.DELETE) - 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=value, + change_type="delete" + )) else: conf = SystemConfig(key=key, value=value) conf.create(self._db) # 发送配置变更通知 - event = ConfigChangeEvent(key, old_value=None, new_value=value, - change_type=ConfigChangeType.ADD) - 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=value, + change_type="add" + )) def get(self, key: Union[str, SystemConfigKey] = None) -> Any: """ @@ -81,9 +93,13 @@ class SystemConfigOper(DbOper, metaclass=Singleton): if conf: conf.delete(self._db, conf.id) # 发送配置变更通知 - event = ConfigChangeEvent(key, old_value=old_value, new_value=None, - change_type=ConfigChangeType.ADD) - 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=None, + change_type="delete" + )) return True def __del__(self): diff --git a/app/modules/emby/__init__.py b/app/modules/emby/__init__.py index 0a3e53d3..adaef558 100644 --- a/app/modules/emby/__init__.py +++ b/app/modules/emby/__init__.py @@ -1,18 +1,16 @@ from typing import Any, Generator, List, Optional, Tuple, Union from app import schemas -from app.core.config import on_config_change from app.core.context import MediaInfo -from app.core.event import eventmanager +from app.core.event import eventmanager, Event from app.log import logger from app.modules import _MediaServerBase, _ModuleBase from app.modules.emby.emby import Emby -from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType, SystemConfigKey +from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType, SystemConfigKey, EventType class EmbyModule(_ModuleBase, _MediaServerBase[Emby]): - @on_config_change([SystemConfigKey.MediaServers.value]) def init_module(self) -> None: """ 初始化模块 @@ -20,6 +18,19 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]): super().init_service(service_name=Emby.__name__.lower(), service_type=lambda conf: Emby(**conf.config, sync_libraries=conf.sync_libraries)) + @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 [SystemConfigKey.MediaServers.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "Emby" @@ -271,7 +282,8 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]): ) for season, episodes in seasoninfo.items()] def mediaserver_playing(self, server: str, - count: Optional[int] = 20, username: Optional[str] = None) -> List[schemas.MediaServerPlayItem]: + count: Optional[int] = 20, username: Optional[str] = None) -> List[ + schemas.MediaServerPlayItem]: """ 获取媒体服务器正在播放信息 """ @@ -290,7 +302,8 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]): return server_obj.get_play_url(item_id) def mediaserver_latest(self, server: Optional[str] = None, - count: Optional[int] = 20, username: Optional[str] = None) -> List[schemas.MediaServerPlayItem]: + count: Optional[int] = 20, username: Optional[str] = None) -> List[ + schemas.MediaServerPlayItem]: """ 获取媒体服务器最新入库条目 """ diff --git a/app/modules/jellyfin/__init__.py b/app/modules/jellyfin/__init__.py index 1f196aaa..c87f81db 100644 --- a/app/modules/jellyfin/__init__.py +++ b/app/modules/jellyfin/__init__.py @@ -1,19 +1,17 @@ from typing import Any, Generator, List, Optional, Tuple, Union from app import schemas -from app.core.config import on_config_change from app.core.context import MediaInfo -from app.core.event import eventmanager +from app.core.event import eventmanager, Event from app.log import logger from app.modules import _MediaServerBase, _ModuleBase from app.modules.jellyfin.jellyfin import Jellyfin from app.schemas import AuthCredentials, AuthInterceptCredentials -from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType, SystemConfigKey +from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType, SystemConfigKey, EventType class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]): - @on_config_change([SystemConfigKey.MediaServers.value]) def init_module(self) -> None: """ 初始化模块 @@ -21,6 +19,19 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]): super().init_service(service_name=Jellyfin.__name__.lower(), service_type=lambda conf: Jellyfin(**conf.config, sync_libraries=conf.sync_libraries)) + @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 [SystemConfigKey.MediaServers.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "Jellyfin" diff --git a/app/modules/plex/__init__.py b/app/modules/plex/__init__.py index a87feaa8..b526e8e3 100644 --- a/app/modules/plex/__init__.py +++ b/app/modules/plex/__init__.py @@ -1,19 +1,17 @@ from typing import Optional, Tuple, Union, Any, List, Generator from app import schemas -from app.core.config import on_config_change from app.core.context import MediaInfo -from app.core.event import eventmanager +from app.core.event import eventmanager, Event from app.log import logger from app.modules import _ModuleBase, _MediaServerBase from app.modules.plex.plex import Plex from app.schemas import AuthCredentials, AuthInterceptCredentials -from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType, SystemConfigKey +from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType, SystemConfigKey, EventType class PlexModule(_ModuleBase, _MediaServerBase[Plex]): - @on_config_change([SystemConfigKey.MediaServers.value]) def init_module(self) -> None: """ 初始化模块 @@ -21,6 +19,19 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]): super().init_service(service_name=Plex.__name__.lower(), service_type=lambda conf: Plex(**conf.config, sync_libraries=conf.sync_libraries)) + @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 [SystemConfigKey.MediaServers.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "Plex" diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index 9d438621..4ac482be 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -5,19 +5,19 @@ from qbittorrentapi import TorrentFilesList from torrentool.torrent import Torrent from app import schemas -from app.core.config import settings, on_config_change +from app.core.config import settings from app.core.metainfo import MetaInfo +from app.core.event import eventmanager, Event from app.log import logger from app.modules import _ModuleBase, _DownloaderBase from app.modules.qbittorrent.qbittorrent import Qbittorrent from app.schemas import TransferTorrent, DownloadingTorrent -from app.schemas.types import TorrentStatus, ModuleType, DownloaderType, SystemConfigKey +from app.schemas.types import TorrentStatus, ModuleType, DownloaderType, SystemConfigKey, EventType from app.utils.string import StringUtils class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]): - @on_config_change([SystemConfigKey.Downloaders.value]) def init_module(self) -> None: """ 初始化模块 @@ -25,6 +25,19 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]): super().init_service(service_name=Qbittorrent.__name__.lower(), service_type=Qbittorrent) + @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 [SystemConfigKey.Downloaders.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "Qbittorrent" @@ -287,7 +300,8 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]): dlspeed=StringUtils.str_filesize(torrent.get('dlspeed')), upspeed=StringUtils.str_filesize(torrent.get('upspeed')), left_time=StringUtils.str_secends( - (torrent.get('total_size') - torrent.get('completed')) / torrent.get('dlspeed')) if torrent.get( + (torrent.get('total_size') - torrent.get('completed')) / torrent.get( + 'dlspeed')) if torrent.get( 'dlspeed') > 0 else '' )) else: diff --git a/app/modules/slack/__init__.py b/app/modules/slack/__init__.py index b6b7b782..95dd9fde 100644 --- a/app/modules/slack/__init__.py +++ b/app/modules/slack/__init__.py @@ -2,18 +2,17 @@ import json import re from typing import Optional, Union, List, Tuple, Any -from app.core.config import on_config_change from app.core.context import MediaInfo, Context +from app.core.event import eventmanager, Event from app.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.slack.slack import Slack -from app.schemas import MessageChannel, CommingMessage, Notification -from app.schemas.types import ModuleType, SystemConfigKey +from app.schemas import MessageChannel, CommingMessage, Notification, ConfigChangeEventData +from app.schemas.types import ModuleType, SystemConfigKey, EventType class SlackModule(_ModuleBase, _MessageBase[Slack]): - @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 @@ -22,6 +21,19 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]): service_type=Slack) self._channel = MessageChannel.Slack + @eventmanager.register(EventType.ConfigChanged) + def handle_config_changed(self, event: Event): + """ + 处理配置变更事件 + :param event: 事件对象 + """ + if not event: + return + event_data: ConfigChangeEventData = event.event_data + if event_data.key not in [SystemConfigKey.Notifications.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "Slack" diff --git a/app/modules/synologychat/__init__.py b/app/modules/synologychat/__init__.py index 8e789dc9..b849622a 100644 --- a/app/modules/synologychat/__init__.py +++ b/app/modules/synologychat/__init__.py @@ -1,17 +1,16 @@ from typing import Optional, Union, List, Tuple, Any -from app.core.config import on_config_change from app.core.context import MediaInfo, Context +from app.core.event import eventmanager, Event from app.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.synologychat.synologychat import SynologyChat -from app.schemas import MessageChannel, CommingMessage, Notification -from app.schemas.types import ModuleType, SystemConfigKey +from app.schemas import MessageChannel, CommingMessage, Notification, ConfigChangeEventData +from app.schemas.types import ModuleType, SystemConfigKey, EventType class SynologyChatModule(_ModuleBase, _MessageBase[SynologyChat]): - @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 @@ -20,6 +19,19 @@ class SynologyChatModule(_ModuleBase, _MessageBase[SynologyChat]): service_type=SynologyChat) self._channel = MessageChannel.SynologyChat + @eventmanager.register(EventType.ConfigChanged) + def handle_config_changed(self, event: Event): + """ + 处理配置变更事件 + :param event: 事件对象 + """ + if not event: + return + event_data: ConfigChangeEventData = event.event_data + if event_data.key not in [SystemConfigKey.Notifications.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "Synology Chat" diff --git a/app/modules/telegram/__init__.py b/app/modules/telegram/__init__.py index cbb3a5e3..14b31669 100644 --- a/app/modules/telegram/__init__.py +++ b/app/modules/telegram/__init__.py @@ -1,21 +1,21 @@ import copy import json -from typing import Optional, Union, List, Tuple, Any, Dict +from typing import Dict +from typing import Optional, Union, List, Tuple, Any -from app.core.config import on_config_change from app.core.context import MediaInfo, Context +from app.core.event import Event from app.core.event import eventmanager from app.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.telegram.telegram import Telegram -from app.schemas import MessageChannel, CommingMessage, Notification, CommandRegisterEventData -from app.schemas.types import ModuleType, ChainEventType, SystemConfigKey +from app.schemas import MessageChannel, CommingMessage, Notification, CommandRegisterEventData, ConfigChangeEventData +from app.schemas.types import ModuleType, ChainEventType, SystemConfigKey, EventType from app.utils.structures import DictUtils class TelegramModule(_ModuleBase, _MessageBase[Telegram]): - @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 @@ -24,6 +24,19 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]): service_type=Telegram) self._channel = MessageChannel.Telegram + @eventmanager.register(EventType.ConfigChanged) + def handle_config_changed(self, event: Event): + """ + 处理配置变更事件 + :param event: 事件对象 + """ + if not event: + return + event_data: ConfigChangeEventData = event.event_data + if event_data.key not in [SystemConfigKey.Notifications.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "Telegram" diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index e0aff549..8c982d19 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -5,19 +5,19 @@ from torrentool.torrent import Torrent from transmission_rpc import File from app import schemas -from app.core.config import settings, on_config_change +from app.core.config import settings from app.core.metainfo import MetaInfo +from app.core.event import eventmanager, Event from app.log import logger from app.modules import _ModuleBase, _DownloaderBase from app.modules.transmission.transmission import Transmission from app.schemas import TransferTorrent, DownloadingTorrent -from app.schemas.types import TorrentStatus, ModuleType, DownloaderType, SystemConfigKey +from app.schemas.types import TorrentStatus, ModuleType, DownloaderType, SystemConfigKey, EventType from app.utils.string import StringUtils class TransmissionModule(_ModuleBase, _DownloaderBase[Transmission]): - @on_config_change([SystemConfigKey.Downloaders.value]) def init_module(self) -> None: """ 初始化模块 @@ -25,6 +25,19 @@ class TransmissionModule(_ModuleBase, _DownloaderBase[Transmission]): super().init_service(service_name=Transmission.__name__.lower(), service_type=Transmission) + @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 [SystemConfigKey.Downloaders.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "Transmission" diff --git a/app/modules/trimemedia/__init__.py b/app/modules/trimemedia/__init__.py index af9e66c6..b8ec1520 100644 --- a/app/modules/trimemedia/__init__.py +++ b/app/modules/trimemedia/__init__.py @@ -1,19 +1,17 @@ from typing import Any, Generator, List, Optional, Tuple, Union from app import schemas -from app.core.config import on_config_change from app.core.context import MediaInfo -from app.core.event import eventmanager +from app.core.event import eventmanager, Event from app.log import logger from app.modules import _MediaServerBase, _ModuleBase from app.modules.trimemedia.trimemedia import TrimeMedia from app.schemas import AuthCredentials, AuthInterceptCredentials -from app.schemas.types import ChainEventType, MediaServerType, MediaType, ModuleType, SystemConfigKey +from app.schemas.types import ChainEventType, MediaServerType, MediaType, ModuleType, SystemConfigKey, EventType class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): - @on_config_change([SystemConfigKey.MediaServers.value]) def init_module(self) -> None: """ 初始化模块 @@ -25,6 +23,19 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): ), ) + @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 [SystemConfigKey.MediaServers.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "飞牛影视" diff --git a/app/modules/vocechat/__init__.py b/app/modules/vocechat/__init__.py index 8a7bc26b..26edc4d0 100644 --- a/app/modules/vocechat/__init__.py +++ b/app/modules/vocechat/__init__.py @@ -1,18 +1,17 @@ import json from typing import Optional, Union, List, Tuple, Any, Dict -from app.core.config import on_config_change from app.core.context import Context, MediaInfo +from app.core.event import eventmanager, Event from app.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.vocechat.vocechat import VoceChat -from app.schemas import MessageChannel, CommingMessage, Notification -from app.schemas.types import ModuleType, SystemConfigKey +from app.schemas import MessageChannel, CommingMessage, Notification, ConfigChangeEventData +from app.schemas.types import ModuleType, SystemConfigKey, EventType class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]): - @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 @@ -21,6 +20,19 @@ class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]): service_type=VoceChat) self._channel = MessageChannel.VoceChat + @eventmanager.register(EventType.ConfigChanged) + def handle_config_changed(self, event: Event): + """ + 处理配置变更事件 + :param event: 事件对象 + """ + if not event: + return + event_data: ConfigChangeEventData = event.event_data + if event_data.key not in [SystemConfigKey.Notifications.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "VoceChat" diff --git a/app/modules/webpush/__init__.py b/app/modules/webpush/__init__.py index 09f36865..9c5082ba 100644 --- a/app/modules/webpush/__init__.py +++ b/app/modules/webpush/__init__.py @@ -3,16 +3,16 @@ from typing import Union, Tuple from pywebpush import webpush, WebPushException -from app.core.config import global_vars, settings, on_config_change +from app.core.config import global_vars, settings +from app.core.event import eventmanager, Event from app.log import logger from app.modules import _ModuleBase, _MessageBase -from app.schemas import Notification -from app.schemas.types import ModuleType, MessageChannel, SystemConfigKey +from app.schemas import Notification, ConfigChangeEventData +from app.schemas.types import ModuleType, MessageChannel, SystemConfigKey, EventType class WebPushModule(_ModuleBase, _MessageBase): - @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 @@ -20,6 +20,19 @@ class WebPushModule(_ModuleBase, _MessageBase): super().init_service(service_name=self.get_name().lower()) self._channel = MessageChannel.WebPush + @eventmanager.register(EventType.ConfigChanged) + def handle_config_changed(self, event: Event): + """ + 处理配置变更事件 + :param event: 事件对象 + """ + if not event: + return + event_data: ConfigChangeEventData = event.event_data + if event_data.key not in [SystemConfigKey.Notifications.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "WebPush" diff --git a/app/modules/wechat/__init__.py b/app/modules/wechat/__init__.py index 587b8bf7..b5bd4c20 100644 --- a/app/modules/wechat/__init__.py +++ b/app/modules/wechat/__init__.py @@ -2,22 +2,20 @@ import copy import xml.dom.minidom from typing import Optional, Union, List, Tuple, Any, Dict -from app.core.config import on_config_change from app.core.context import Context, MediaInfo -from app.core.event import eventmanager +from app.core.event import Event, eventmanager from app.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.wechat.WXBizMsgCrypt3 import WXBizMsgCrypt from app.modules.wechat.wechat import WeChat -from app.schemas import MessageChannel, CommingMessage, Notification, CommandRegisterEventData -from app.schemas.types import ModuleType, ChainEventType, SystemConfigKey +from app.schemas import MessageChannel, CommingMessage, Notification, CommandRegisterEventData, ConfigChangeEventData +from app.schemas.types import ModuleType, ChainEventType, SystemConfigKey, EventType from app.utils.dom import DomUtils from app.utils.structures import DictUtils class WechatModule(_ModuleBase, _MessageBase[WeChat]): - @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 @@ -26,6 +24,19 @@ class WechatModule(_ModuleBase, _MessageBase[WeChat]): service_type=WeChat) self._channel = MessageChannel.Wechat + @eventmanager.register(EventType.ConfigChanged) + def handle_config_changed(self, event: Event): + """ + 处理配置变更事件 + :param event: 事件对象 + """ + if not event: + return + event_data: ConfigChangeEventData = event.event_data + if event_data.key not in [SystemConfigKey.Notifications.value]: + return + self.init_module() + @staticmethod def get_name() -> str: return "微信" diff --git a/app/monitor.py b/app/monitor.py index 05cf811a..659c9072 100644 --- a/app/monitor.py +++ b/app/monitor.py @@ -14,14 +14,16 @@ from watchdog.observers.polling import PollingObserver from app.chain import ChainBase from app.chain.storage import StorageChain from app.chain.transfer import TransferChain -from app.core.config import settings, on_config_change +from app.core.config import settings from app.db.systemconfig_oper import SystemConfigOper from app.helper.directory import DirectoryHelper from app.helper.message import MessageHelper from app.log import logger from app.schemas import FileItem -from app.schemas.types import SystemConfigKey +from app.schemas.types import SystemConfigKey, EventType from app.utils.singleton import Singleton +from app.core.event import Event, eventmanager +from app.schemas import ConfigChangeEventData lock = Lock() snapshot_lock = Lock() @@ -86,7 +88,19 @@ class Monitor(metaclass=Singleton): # 启动目录监控和文件整理 self.init() - @on_config_change([SystemConfigKey.Directories.value]) + @eventmanager.register(EventType.ConfigChanged) + def handle_config_changed(self, event: Event): + """ + 处理配置变更事件 + :param event: 事件对象 + """ + if not event: + return + event_data: ConfigChangeEventData = event.event_data + if event_data.key not in [SystemConfigKey.Directories.value]: + return + self.init() + def init(self): """ 启动监控 diff --git a/app/scheduler.py b/app/scheduler.py index 6002cecc..fbfedfa6 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -18,18 +18,17 @@ from app.chain.subscribe import SubscribeChain from app.chain.tmdb import TmdbChain from app.chain.transfer import TransferChain from app.chain.workflow import WorkflowChain -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, eventmanager, Event from app.core.plugin import PluginManager from app.db.systemconfig_oper import SystemConfigOper from app.helper.sites import SitesHelper from app.log import logger -from app.schemas import Notification, NotificationType, Workflow +from app.schemas import Notification, NotificationType, Workflow, ConfigChangeEventData from app.schemas.types import EventType, SystemConfigKey from app.utils.singleton import Singleton from app.utils.timer import TimerUtils - lock = threading.Lock() @@ -57,8 +56,20 @@ class Scheduler(metaclass=Singleton): def __init__(self): self.init() - @on_config_change(['DEV', 'COOKIECLOUD_INTERVAL', 'MEDIASERVER_SYNC_INTERVAL', 'SUBSCRIBE_SEARCH', - 'SUBSCRIBE_MODE', 'SUBSCRIBE_RSS_INTERVAL', 'SITEDATA_REFRESH_INTERVAL']) + @eventmanager.register(EventType.ConfigChanged) + def handle_config_changed(self, event: Event): + """ + 处理配置变更事件 + :param event: 事件对象 + """ + if not event: + return + event_data: ConfigChangeEventData = event.event_data + if event_data.key not in ['DEV', 'COOKIECLOUD_INTERVAL', 'MEDIASERVER_SYNC_INTERVAL', 'SUBSCRIBE_SEARCH', + 'SUBSCRIBE_MODE', 'SUBSCRIBE_RSS_INTERVAL', 'SITEDATA_REFRESH_INTERVAL']: + return + self.init() + def init(self): """ 初始化定时服务 diff --git a/app/schemas/event.py b/app/schemas/event.py index bf89c793..0efe01b1 100644 --- a/app/schemas/event.py +++ b/app/schemas/event.py @@ -22,6 +22,16 @@ class BaseEventData(BaseModel): pass +class ConfigChangeEventData(BaseEventData): + """ + ConfigChange 事件的数据模型 + """ + key: str = Field(..., description="配置项的键") + old_value: Optional[Any] = Field(default=None, description="配置项的旧值") + new_value: Optional[Any] = Field(default=None, description="配置项的新值") + change_type: str = Field(default="update", description="配置项的变更类型,如 'add', 'update', 'delete'") + + class ChainEventData(BaseEventData): """ 链式事件数据的基类,所有具体事件数据类应继承自此类 diff --git a/app/schemas/types.py b/app/schemas/types.py index 91c1bd1a..5015ed11 100644 --- a/app/schemas/types.py +++ b/app/schemas/types.py @@ -61,6 +61,8 @@ class EventType(Enum): MetadataScrape = "metadata.scrape" # 模块需要重载 ModuleReload = "module.reload" + # 配置项更新 + ConfigChanged = "config.updated" # 同步链式事件