diff --git a/app/api/endpoints/system.py b/app/api/endpoints/system.py index 2840558d..24db7ea8 100644 --- a/app/api/endpoints/system.py +++ b/app/api/endpoints/system.py @@ -24,14 +24,13 @@ from app.db.models import User from app.db.systemconfig_oper import SystemConfigOper from app.db.user_oper import get_current_active_superuser from app.helper.mediaserver import MediaServerHelper -from app.helper.message import MessageHelper, MessageQueueManager +from app.helper.message import MessageHelper from app.helper.progress import ProgressHelper from app.helper.rule import RuleHelper from app.helper.sites import SitesHelper from app.helper.subscribe import SubscribeHelper from app.helper.system import SystemHelper from app.log import logger -from app.monitor import Monitor from app.scheduler import Scheduler from app.schemas.types import SystemConfigKey from app.utils.crypto import HashUtils @@ -483,18 +482,6 @@ def restart_system(_: User = Depends(get_current_active_superuser)): return schemas.Response(success=ret, message=msg) -@router.get("/reload", summary="重新加载模块", response_model=schemas.Response) -def reload_module(_: User = Depends(get_current_active_superuser)): - """ - 重新加载模块(仅管理员) - """ - MessageQueueManager().init_config() - ModuleManager().reload() - Scheduler().init() - Monitor().init() - return schemas.Response(success=True) - - @router.get("/runscheduler", summary="运行服务", response_model=schemas.Response) def run_scheduler(jobid: str, _: User = Depends(get_current_active_superuser)): diff --git a/app/core/config.py b/app/core/config.py index 2c65d3ff..26f0557e 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,21 +1,120 @@ import copy import json import os -import re 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 +from typing import Any, Dict, List, Optional, Tuple, Type, Callable 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() + + class ConfigModel(BaseModel): """ Pydantic 配置模型,描述所有配置项及其类型和默认值 @@ -255,30 +354,26 @@ class ConfigModel(BaseModel): # 编码探测的最低置信度阈值 ENCODING_DETECTION_MIN_CONFIDENCE: float = 0.8 # 允许的图片缓存域名 - SECURITY_IMAGE_DOMAINS: List[str] = Field( - default_factory=lambda: ["image.tmdb.org", - "static-mdb.v.geilijiasu.com", - "bing.com", - "doubanio.com", - "lain.bgm.tv", - "raw.githubusercontent.com", - "github.com", - "thetvdb.com", - "cctvpic.com", - "iqiyipic.com", - "hdslb.com", - "cmvideo.cn", - "ykimg.com", - "qpic.cn"] - ) + SECURITY_IMAGE_DOMAINS: list = Field(default=[ + "image.tmdb.org", + "static-mdb.v.geilijiasu.com", + "bing.com", + "doubanio.com", + "lain.bgm.tv", + "raw.githubusercontent.com", + "github.com", + "thetvdb.com", + "cctvpic.com", + "iqiyipic.com", + "hdslb.com", + "cmvideo.cn", + "ykimg.com", + "qpic.cn" + ]) # 允许的图片文件后缀格式 - SECURITY_IMAGE_SUFFIXES: List[str] = Field( - default_factory=lambda: [".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".avif"] - ) + SECURITY_IMAGE_SUFFIXES: list = Field(default=[".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".avif"]) # 重命名时支持的S0别名 - RENAME_FORMAT_S0_NAMES: List[str] = Field( - default_factory=lambda: ["Specials", "SPs"] - ) + RENAME_FORMAT_S0_NAMES: list = Field(default=["Specials", "SPs"]) # 启用分词搜索 TOKENIZED_SEARCH: bool = False # 为指定默认字幕添加.default后缀 @@ -333,6 +428,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel): raise_exception: bool = False) -> Tuple[Any, bool]: """ 通用类型转换函数,根据预期类型转换值。如果转换失败,返回默认值 + :return: 元组 (转换后的值, 是否需要更新) """ if isinstance(value, (list, dict, set)): value = copy.deepcopy(value) @@ -373,12 +469,8 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel): converted = float(value) return converted, str(converted) != str(original_value) elif expected_type is str: - # 清理 value 中所有空白字符的字段 - fields_not_keep_spaces = {"AUTO_DOWNLOAD_USER", "REPO_GITHUB_TOKEN", "PLUGIN_MARKET"} - if field_name in fields_not_keep_spaces: - value = re.sub(r"\s+", "", value) - return value, str(value) != str(original_value) - # 支持 list 类型的处理 + converted = str(value).strip() + return converted, converted != str(original_value) elif expected_type is list: if isinstance(value, list): return value, str(value) != str(original_value) @@ -388,7 +480,6 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel): return items, items != original_value else: return items, str(items) != str(original_value) - # 可根据需要添加更多类型处理 else: return value, str(value) != str(original_value) except (ValueError, TypeError) as e: @@ -462,9 +553,14 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel): success, message = self.update_env_config(field, value, converted_value) # 仅成功更新配置时,才更新内存 if success: + old_value = getattr(self, key) setattr(self, key, converted_value) if hasattr(log_settings, key): setattr(log_settings, key, converted_value) + # 发送配置变更通知 + event = ConfigChangeEvent(key, old_value, converted_value) + config_notifier.notify(event) + return success, message return True, "" except Exception as e: @@ -475,21 +571,8 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel): 更新多个配置项 """ results = {} - log_updated, plugin_monitor_updated = False, False for k, v in env.items(): results[k] = self.update_setting(k, v) - if hasattr(log_settings, k): - log_updated = True - if k in ["PLUGIN_AUTO_RELOAD", "DEV"]: - plugin_monitor_updated = True - # 本次更新存在日志配置项更新,需要重新加载日志配置 - if log_updated: - logger.update_loggers() - # 本次更新存在插件监控配置项更新,需要重新加载插件监控 - if plugin_monitor_updated: - # 解决顶层循环导入问题 - from app.core.plugin import PluginManager - PluginManager().reload_monitor() return results @property @@ -645,6 +728,10 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel): return UrlUtils.combine_url(host=self.APP_DOMAIN, path=url) +# 实例化配置 +settings = Settings() + + class GlobalVar(object): """ 全局标识 @@ -702,8 +789,93 @@ class GlobalVar(object): return self.is_system_stopped or workflow_id in self.EMERGENCY_STOP_WORKFLOWS -# 实例化配置 -settings = Settings() - # 全局标识 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/plugin.py b/app/core/plugin.py index 5d44796d..405bf364 100644 --- a/app/core/plugin.py +++ b/app/core/plugin.py @@ -15,7 +15,7 @@ from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer from app import schemas -from app.core.config import settings +from app.core.config import settings, on_config_change from app.core.event import eventmanager from app.db.plugindata_oper import PluginDataOper from app.db.systemconfig_oper import SystemConfigOper @@ -241,6 +241,13 @@ class PluginManager(metaclass=Singleton): """ return self._plugins + @on_config_change(['PLUGIN_AUTO_RELOAD', 'DEV']) + def handle_config_change(self): + """ + 处理配置变更事件,重新加载插件监测 + """ + self.reload_monitor() + def reload_monitor(self): """ 重新加载插件文件修改监测 diff --git a/app/db/systemconfig_oper.py b/app/db/systemconfig_oper.py index 9eaf05d1..78c554d9 100644 --- a/app/db/systemconfig_oper.py +++ b/app/db/systemconfig_oper.py @@ -1,5 +1,6 @@ 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 @@ -24,17 +25,32 @@ class SystemConfigOper(DbOper, metaclass=Singleton): """ if isinstance(key, SystemConfigKey): key = key.value + # 旧值 + old_value = self.__SYSTEMCONF.get(key) # 更新内存 self.__SYSTEMCONF[key] = value conf = SystemConfig.get_by_key(self._db, key) if conf: if value: 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) 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) 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) def get(self, key: Union[str, SystemConfigKey] = None) -> Any: """ @@ -59,11 +75,15 @@ class SystemConfigOper(DbOper, metaclass=Singleton): if isinstance(key, SystemConfigKey): key = key.value # 更新内存 - self.__SYSTEMCONF.pop(key, None) + old_value = self.__SYSTEMCONF.pop(key, None) # 写入数据库 conf = SystemConfig.get_by_key(self._db, key) 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) return True def __del__(self): diff --git a/app/modules/emby/__init__.py b/app/modules/emby/__init__.py index a481aaa3..0a3e53d3 100644 --- a/app/modules/emby/__init__.py +++ b/app/modules/emby/__init__.py @@ -1,16 +1,18 @@ 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.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 +from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType, SystemConfigKey class EmbyModule(_ModuleBase, _MediaServerBase[Emby]): + @on_config_change([SystemConfigKey.MediaServers.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/jellyfin/__init__.py b/app/modules/jellyfin/__init__.py index 7268f1c8..1f196aaa 100644 --- a/app/modules/jellyfin/__init__.py +++ b/app/modules/jellyfin/__init__.py @@ -1,17 +1,19 @@ 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.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 +from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType, SystemConfigKey class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]): + @on_config_change([SystemConfigKey.MediaServers.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/plex/__init__.py b/app/modules/plex/__init__.py index 09193ce5..a87feaa8 100644 --- a/app/modules/plex/__init__.py +++ b/app/modules/plex/__init__.py @@ -1,17 +1,19 @@ 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.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 +from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType, SystemConfigKey class PlexModule(_ModuleBase, _MediaServerBase[Plex]): + @on_config_change([SystemConfigKey.MediaServers.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index caf1693d..9d438621 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -5,18 +5,19 @@ from qbittorrentapi import TorrentFilesList from torrentool.torrent import Torrent from app import schemas -from app.core.config import settings +from app.core.config import settings, on_config_change from app.core.metainfo import MetaInfo 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 +from app.schemas.types import TorrentStatus, ModuleType, DownloaderType, SystemConfigKey from app.utils.string import StringUtils class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]): + @on_config_change([SystemConfigKey.Downloaders.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/slack/__init__.py b/app/modules/slack/__init__.py index 1c99f0e5..b6b7b782 100644 --- a/app/modules/slack/__init__.py +++ b/app/modules/slack/__init__.py @@ -2,16 +2,18 @@ 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.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 +from app.schemas.types import ModuleType, SystemConfigKey class SlackModule(_ModuleBase, _MessageBase[Slack]): + @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/synologychat/__init__.py b/app/modules/synologychat/__init__.py index 84248ff2..8e789dc9 100644 --- a/app/modules/synologychat/__init__.py +++ b/app/modules/synologychat/__init__.py @@ -1,15 +1,17 @@ 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.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 +from app.schemas.types import ModuleType, SystemConfigKey class SynologyChatModule(_ModuleBase, _MessageBase[SynologyChat]): + @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/telegram/__init__.py b/app/modules/telegram/__init__.py index 52dc0d61..cbb3a5e3 100644 --- a/app/modules/telegram/__init__.py +++ b/app/modules/telegram/__init__.py @@ -2,18 +2,20 @@ import copy import json from typing import Optional, Union, List, Tuple, Any, Dict +from app.core.config import on_config_change from app.core.context import MediaInfo, Context 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 +from app.schemas.types import ModuleType, ChainEventType, SystemConfigKey from app.utils.structures import DictUtils class TelegramModule(_ModuleBase, _MessageBase[Telegram]): + @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index 47ae8d79..e0aff549 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -5,18 +5,19 @@ from torrentool.torrent import Torrent from transmission_rpc import File from app import schemas -from app.core.config import settings +from app.core.config import settings, on_config_change from app.core.metainfo import MetaInfo 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 +from app.schemas.types import TorrentStatus, ModuleType, DownloaderType, SystemConfigKey from app.utils.string import StringUtils class TransmissionModule(_ModuleBase, _DownloaderBase[Transmission]): + @on_config_change([SystemConfigKey.Downloaders.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/trimemedia/__init__.py b/app/modules/trimemedia/__init__.py index be12ec18..af9e66c6 100644 --- a/app/modules/trimemedia/__init__.py +++ b/app/modules/trimemedia/__init__.py @@ -1,17 +1,19 @@ 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.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 +from app.schemas.types import ChainEventType, MediaServerType, MediaType, ModuleType, SystemConfigKey class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): + @on_config_change([SystemConfigKey.MediaServers.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/vocechat/__init__.py b/app/modules/vocechat/__init__.py index 9f7b8ff6..8a7bc26b 100644 --- a/app/modules/vocechat/__init__.py +++ b/app/modules/vocechat/__init__.py @@ -1,16 +1,18 @@ 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.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 +from app.schemas.types import ModuleType, SystemConfigKey class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]): + @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/webpush/__init__.py b/app/modules/webpush/__init__.py index bb843490..09f36865 100644 --- a/app/modules/webpush/__init__.py +++ b/app/modules/webpush/__init__.py @@ -3,15 +3,16 @@ from typing import Union, Tuple from pywebpush import webpush, WebPushException -from app.core.config import global_vars, settings +from app.core.config import global_vars, settings, on_config_change from app.log import logger from app.modules import _ModuleBase, _MessageBase from app.schemas import Notification -from app.schemas.types import ModuleType, MessageChannel +from app.schemas.types import ModuleType, MessageChannel, SystemConfigKey class WebPushModule(_ModuleBase, _MessageBase): + @on_config_change([SystemConfigKey.Notifications.value]) def init_module(self) -> None: """ 初始化模块 diff --git a/app/modules/wechat/__init__.py b/app/modules/wechat/__init__.py index 059afd2f..587b8bf7 100644 --- a/app/modules/wechat/__init__.py +++ b/app/modules/wechat/__init__.py @@ -2,6 +2,7 @@ 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.log import logger @@ -9,13 +10,14 @@ 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 +from app.schemas.types import ModuleType, ChainEventType, SystemConfigKey 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: """ 初始化模块 diff --git a/app/monitor.py b/app/monitor.py index 24007afd..05cf811a 100644 --- a/app/monitor.py +++ b/app/monitor.py @@ -14,12 +14,13 @@ 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 +from app.core.config import settings, on_config_change 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.utils.singleton import Singleton lock = Lock() @@ -85,6 +86,7 @@ class Monitor(metaclass=Singleton): # 启动目录监控和文件整理 self.init() + @on_config_change([SystemConfigKey.Directories.value]) def init(self): """ 启动监控 diff --git a/app/scheduler.py b/app/scheduler.py index 9d0f3be5..6002cecc 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -18,7 +18,7 @@ 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 +from app.core.config import settings, on_config_change from app.core.event import EventManager from app.core.plugin import PluginManager from app.db.systemconfig_oper import SystemConfigOper @@ -57,6 +57,8 @@ 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']) def init(self): """ 初始化定时服务