fix Config reload

This commit is contained in:
jxxghp
2025-06-03 23:08:58 +08:00
parent b4ed2880f7
commit bbfd8ca3f5
22 changed files with 338 additions and 280 deletions

View File

@@ -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="所有配置项更新成功",

View File

@@ -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()

View File

@@ -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,
{

View File

@@ -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())

View File

@@ -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):

View File

@@ -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):

View File

@@ -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]:
"""
获取媒体服务器最新入库条目
"""

View File

@@ -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"

View File

@@ -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"

View File

@@ -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:

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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 "飞牛影视"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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 "微信"

View File

@@ -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):
"""
启动监控

View File

@@ -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):
"""
初始化定时服务

View File

@@ -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):
"""
链式事件数据的基类,所有具体事件数据类应继承自此类

View File

@@ -61,6 +61,8 @@ class EventType(Enum):
MetadataScrape = "metadata.scrape"
# 模块需要重载
ModuleReload = "module.reload"
# 配置项更新
ConfigChanged = "config.updated"
# 同步链式事件