mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-22 08:43:37 +08:00
refactor:重构配置热加载
This commit is contained in:
@@ -24,14 +24,13 @@ from app.db.models import User
|
|||||||
from app.db.systemconfig_oper import SystemConfigOper
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
from app.db.user_oper import get_current_active_superuser
|
from app.db.user_oper import get_current_active_superuser
|
||||||
from app.helper.mediaserver import MediaServerHelper
|
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.progress import ProgressHelper
|
||||||
from app.helper.rule import RuleHelper
|
from app.helper.rule import RuleHelper
|
||||||
from app.helper.sites import SitesHelper
|
from app.helper.sites import SitesHelper
|
||||||
from app.helper.subscribe import SubscribeHelper
|
from app.helper.subscribe import SubscribeHelper
|
||||||
from app.helper.system import SystemHelper
|
from app.helper.system import SystemHelper
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.monitor import Monitor
|
|
||||||
from app.scheduler import Scheduler
|
from app.scheduler import Scheduler
|
||||||
from app.schemas.types import SystemConfigKey
|
from app.schemas.types import SystemConfigKey
|
||||||
from app.utils.crypto import HashUtils
|
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)
|
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)
|
@router.get("/runscheduler", summary="运行服务", response_model=schemas.Response)
|
||||||
def run_scheduler(jobid: str,
|
def run_scheduler(jobid: str,
|
||||||
_: User = Depends(get_current_active_superuser)):
|
_: User = Depends(get_current_active_superuser)):
|
||||||
|
|||||||
@@ -1,21 +1,120 @@
|
|||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import secrets
|
import secrets
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
from collections import defaultdict
|
||||||
|
from enum import Enum
|
||||||
from pathlib import Path
|
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 dotenv import set_key
|
||||||
from pydantic import BaseModel, BaseSettings, validator, Field
|
from pydantic import BaseModel, BaseSettings, validator, Field
|
||||||
|
|
||||||
from app.log import logger, log_settings, LogConfigModel
|
from app.log import logger, log_settings, LogConfigModel
|
||||||
|
from app.utils.object import ObjectUtils
|
||||||
from app.utils.system import SystemUtils
|
from app.utils.system import SystemUtils
|
||||||
from app.utils.url import UrlUtils
|
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):
|
class ConfigModel(BaseModel):
|
||||||
"""
|
"""
|
||||||
Pydantic 配置模型,描述所有配置项及其类型和默认值
|
Pydantic 配置模型,描述所有配置项及其类型和默认值
|
||||||
@@ -255,30 +354,26 @@ class ConfigModel(BaseModel):
|
|||||||
# 编码探测的最低置信度阈值
|
# 编码探测的最低置信度阈值
|
||||||
ENCODING_DETECTION_MIN_CONFIDENCE: float = 0.8
|
ENCODING_DETECTION_MIN_CONFIDENCE: float = 0.8
|
||||||
# 允许的图片缓存域名
|
# 允许的图片缓存域名
|
||||||
SECURITY_IMAGE_DOMAINS: List[str] = Field(
|
SECURITY_IMAGE_DOMAINS: list = Field(default=[
|
||||||
default_factory=lambda: ["image.tmdb.org",
|
"image.tmdb.org",
|
||||||
"static-mdb.v.geilijiasu.com",
|
"static-mdb.v.geilijiasu.com",
|
||||||
"bing.com",
|
"bing.com",
|
||||||
"doubanio.com",
|
"doubanio.com",
|
||||||
"lain.bgm.tv",
|
"lain.bgm.tv",
|
||||||
"raw.githubusercontent.com",
|
"raw.githubusercontent.com",
|
||||||
"github.com",
|
"github.com",
|
||||||
"thetvdb.com",
|
"thetvdb.com",
|
||||||
"cctvpic.com",
|
"cctvpic.com",
|
||||||
"iqiyipic.com",
|
"iqiyipic.com",
|
||||||
"hdslb.com",
|
"hdslb.com",
|
||||||
"cmvideo.cn",
|
"cmvideo.cn",
|
||||||
"ykimg.com",
|
"ykimg.com",
|
||||||
"qpic.cn"]
|
"qpic.cn"
|
||||||
)
|
])
|
||||||
# 允许的图片文件后缀格式
|
# 允许的图片文件后缀格式
|
||||||
SECURITY_IMAGE_SUFFIXES: List[str] = Field(
|
SECURITY_IMAGE_SUFFIXES: list = Field(default=[".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".avif"])
|
||||||
default_factory=lambda: [".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".avif"]
|
|
||||||
)
|
|
||||||
# 重命名时支持的S0别名
|
# 重命名时支持的S0别名
|
||||||
RENAME_FORMAT_S0_NAMES: List[str] = Field(
|
RENAME_FORMAT_S0_NAMES: list = Field(default=["Specials", "SPs"])
|
||||||
default_factory=lambda: ["Specials", "SPs"]
|
|
||||||
)
|
|
||||||
# 启用分词搜索
|
# 启用分词搜索
|
||||||
TOKENIZED_SEARCH: bool = False
|
TOKENIZED_SEARCH: bool = False
|
||||||
# 为指定默认字幕添加.default后缀
|
# 为指定默认字幕添加.default后缀
|
||||||
@@ -333,6 +428,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
raise_exception: bool = False) -> Tuple[Any, bool]:
|
raise_exception: bool = False) -> Tuple[Any, bool]:
|
||||||
"""
|
"""
|
||||||
通用类型转换函数,根据预期类型转换值。如果转换失败,返回默认值
|
通用类型转换函数,根据预期类型转换值。如果转换失败,返回默认值
|
||||||
|
:return: 元组 (转换后的值, 是否需要更新)
|
||||||
"""
|
"""
|
||||||
if isinstance(value, (list, dict, set)):
|
if isinstance(value, (list, dict, set)):
|
||||||
value = copy.deepcopy(value)
|
value = copy.deepcopy(value)
|
||||||
@@ -373,12 +469,8 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
converted = float(value)
|
converted = float(value)
|
||||||
return converted, str(converted) != str(original_value)
|
return converted, str(converted) != str(original_value)
|
||||||
elif expected_type is str:
|
elif expected_type is str:
|
||||||
# 清理 value 中所有空白字符的字段
|
converted = str(value).strip()
|
||||||
fields_not_keep_spaces = {"AUTO_DOWNLOAD_USER", "REPO_GITHUB_TOKEN", "PLUGIN_MARKET"}
|
return converted, converted != str(original_value)
|
||||||
if field_name in fields_not_keep_spaces:
|
|
||||||
value = re.sub(r"\s+", "", value)
|
|
||||||
return value, str(value) != str(original_value)
|
|
||||||
# 支持 list 类型的处理
|
|
||||||
elif expected_type is list:
|
elif expected_type is list:
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
return value, str(value) != str(original_value)
|
return value, str(value) != str(original_value)
|
||||||
@@ -388,7 +480,6 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
return items, items != original_value
|
return items, items != original_value
|
||||||
else:
|
else:
|
||||||
return items, str(items) != str(original_value)
|
return items, str(items) != str(original_value)
|
||||||
# 可根据需要添加更多类型处理
|
|
||||||
else:
|
else:
|
||||||
return value, str(value) != str(original_value)
|
return value, str(value) != str(original_value)
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
@@ -462,9 +553,14 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
success, message = self.update_env_config(field, value, converted_value)
|
success, message = self.update_env_config(field, value, converted_value)
|
||||||
# 仅成功更新配置时,才更新内存
|
# 仅成功更新配置时,才更新内存
|
||||||
if success:
|
if success:
|
||||||
|
old_value = getattr(self, key)
|
||||||
setattr(self, key, converted_value)
|
setattr(self, key, converted_value)
|
||||||
if hasattr(log_settings, key):
|
if hasattr(log_settings, key):
|
||||||
setattr(log_settings, key, converted_value)
|
setattr(log_settings, key, converted_value)
|
||||||
|
# 发送配置变更通知
|
||||||
|
event = ConfigChangeEvent(key, old_value, converted_value)
|
||||||
|
config_notifier.notify(event)
|
||||||
|
|
||||||
return success, message
|
return success, message
|
||||||
return True, ""
|
return True, ""
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -475,21 +571,8 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
更新多个配置项
|
更新多个配置项
|
||||||
"""
|
"""
|
||||||
results = {}
|
results = {}
|
||||||
log_updated, plugin_monitor_updated = False, False
|
|
||||||
for k, v in env.items():
|
for k, v in env.items():
|
||||||
results[k] = self.update_setting(k, v)
|
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
|
return results
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -645,6 +728,10 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
return UrlUtils.combine_url(host=self.APP_DOMAIN, path=url)
|
return UrlUtils.combine_url(host=self.APP_DOMAIN, path=url)
|
||||||
|
|
||||||
|
|
||||||
|
# 实例化配置
|
||||||
|
settings = Settings()
|
||||||
|
|
||||||
|
|
||||||
class GlobalVar(object):
|
class GlobalVar(object):
|
||||||
"""
|
"""
|
||||||
全局标识
|
全局标识
|
||||||
@@ -702,8 +789,93 @@ class GlobalVar(object):
|
|||||||
return self.is_system_stopped or workflow_id in self.EMERGENCY_STOP_WORKFLOWS
|
return self.is_system_stopped or workflow_id in self.EMERGENCY_STOP_WORKFLOWS
|
||||||
|
|
||||||
|
|
||||||
# 实例化配置
|
|
||||||
settings = Settings()
|
|
||||||
|
|
||||||
# 全局标识
|
# 全局标识
|
||||||
global_vars = GlobalVar()
|
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()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from watchdog.events import FileSystemEventHandler
|
|||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
|
|
||||||
from app import schemas
|
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.core.event import eventmanager
|
||||||
from app.db.plugindata_oper import PluginDataOper
|
from app.db.plugindata_oper import PluginDataOper
|
||||||
from app.db.systemconfig_oper import SystemConfigOper
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
@@ -241,6 +241,13 @@ class PluginManager(metaclass=Singleton):
|
|||||||
"""
|
"""
|
||||||
return self._plugins
|
return self._plugins
|
||||||
|
|
||||||
|
@on_config_change(['PLUGIN_AUTO_RELOAD', 'DEV'])
|
||||||
|
def handle_config_change(self):
|
||||||
|
"""
|
||||||
|
处理配置变更事件,重新加载插件监测
|
||||||
|
"""
|
||||||
|
self.reload_monitor()
|
||||||
|
|
||||||
def reload_monitor(self):
|
def reload_monitor(self):
|
||||||
"""
|
"""
|
||||||
重新加载插件文件修改监测
|
重新加载插件文件修改监测
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from typing import Any, Union
|
from typing import Any, Union
|
||||||
|
|
||||||
|
from app.core.config import ConfigChangeEvent, config_notifier, ConfigChangeType
|
||||||
from app.db import DbOper
|
from app.db import DbOper
|
||||||
from app.db.models.systemconfig import SystemConfig
|
from app.db.models.systemconfig import SystemConfig
|
||||||
from app.schemas.types import SystemConfigKey
|
from app.schemas.types import SystemConfigKey
|
||||||
@@ -24,17 +25,32 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
|
|||||||
"""
|
"""
|
||||||
if isinstance(key, SystemConfigKey):
|
if isinstance(key, SystemConfigKey):
|
||||||
key = key.value
|
key = key.value
|
||||||
|
# 旧值
|
||||||
|
old_value = self.__SYSTEMCONF.get(key)
|
||||||
# 更新内存
|
# 更新内存
|
||||||
self.__SYSTEMCONF[key] = value
|
self.__SYSTEMCONF[key] = value
|
||||||
conf = SystemConfig.get_by_key(self._db, key)
|
conf = SystemConfig.get_by_key(self._db, key)
|
||||||
if conf:
|
if conf:
|
||||||
if value:
|
if value:
|
||||||
conf.update(self._db, {"value": 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:
|
else:
|
||||||
conf.delete(self._db, conf.id)
|
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:
|
else:
|
||||||
conf = SystemConfig(key=key, value=value)
|
conf = SystemConfig(key=key, value=value)
|
||||||
conf.create(self._db)
|
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:
|
def get(self, key: Union[str, SystemConfigKey] = None) -> Any:
|
||||||
"""
|
"""
|
||||||
@@ -59,11 +75,15 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
|
|||||||
if isinstance(key, SystemConfigKey):
|
if isinstance(key, SystemConfigKey):
|
||||||
key = key.value
|
key = key.value
|
||||||
# 更新内存
|
# 更新内存
|
||||||
self.__SYSTEMCONF.pop(key, None)
|
old_value = self.__SYSTEMCONF.pop(key, None)
|
||||||
# 写入数据库
|
# 写入数据库
|
||||||
conf = SystemConfig.get_by_key(self._db, key)
|
conf = SystemConfig.get_by_key(self._db, key)
|
||||||
if conf:
|
if conf:
|
||||||
conf.delete(self._db, conf.id)
|
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
|
return True
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
from typing import Any, Generator, List, Optional, Tuple, Union
|
from typing import Any, Generator, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
|
from app.core.config import on_config_change
|
||||||
from app.core.context import MediaInfo
|
from app.core.context import MediaInfo
|
||||||
from app.core.event import eventmanager
|
from app.core.event import eventmanager
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _MediaServerBase, _ModuleBase
|
from app.modules import _MediaServerBase, _ModuleBase
|
||||||
from app.modules.emby.emby import Emby
|
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]):
|
class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.MediaServers.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
from typing import Any, Generator, List, Optional, Tuple, Union
|
from typing import Any, Generator, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
|
from app.core.config import on_config_change
|
||||||
from app.core.context import MediaInfo
|
from app.core.context import MediaInfo
|
||||||
from app.core.event import eventmanager
|
from app.core.event import eventmanager
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _MediaServerBase, _ModuleBase
|
from app.modules import _MediaServerBase, _ModuleBase
|
||||||
from app.modules.jellyfin.jellyfin import Jellyfin
|
from app.modules.jellyfin.jellyfin import Jellyfin
|
||||||
from app.schemas import AuthCredentials, AuthInterceptCredentials
|
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]):
|
class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.MediaServers.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
from typing import Optional, Tuple, Union, Any, List, Generator
|
from typing import Optional, Tuple, Union, Any, List, Generator
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
|
from app.core.config import on_config_change
|
||||||
from app.core.context import MediaInfo
|
from app.core.context import MediaInfo
|
||||||
from app.core.event import eventmanager
|
from app.core.event import eventmanager
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _ModuleBase, _MediaServerBase
|
from app.modules import _ModuleBase, _MediaServerBase
|
||||||
from app.modules.plex.plex import Plex
|
from app.modules.plex.plex import Plex
|
||||||
from app.schemas import AuthCredentials, AuthInterceptCredentials
|
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]):
|
class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.MediaServers.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -5,18 +5,19 @@ from qbittorrentapi import TorrentFilesList
|
|||||||
from torrentool.torrent import Torrent
|
from torrentool.torrent import Torrent
|
||||||
|
|
||||||
from app import schemas
|
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.core.metainfo import MetaInfo
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _ModuleBase, _DownloaderBase
|
from app.modules import _ModuleBase, _DownloaderBase
|
||||||
from app.modules.qbittorrent.qbittorrent import Qbittorrent
|
from app.modules.qbittorrent.qbittorrent import Qbittorrent
|
||||||
from app.schemas import TransferTorrent, DownloadingTorrent
|
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
|
from app.utils.string import StringUtils
|
||||||
|
|
||||||
|
|
||||||
class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]):
|
class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.Downloaders.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ import json
|
|||||||
import re
|
import re
|
||||||
from typing import Optional, Union, List, Tuple, Any
|
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.context import MediaInfo, Context
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _ModuleBase, _MessageBase
|
from app.modules import _ModuleBase, _MessageBase
|
||||||
from app.modules.slack.slack import Slack
|
from app.modules.slack.slack import Slack
|
||||||
from app.schemas import MessageChannel, CommingMessage, Notification
|
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]):
|
class SlackModule(_ModuleBase, _MessageBase[Slack]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.Notifications.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
from typing import Optional, Union, List, Tuple, Any
|
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.context import MediaInfo, Context
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _ModuleBase, _MessageBase
|
from app.modules import _ModuleBase, _MessageBase
|
||||||
from app.modules.synologychat.synologychat import SynologyChat
|
from app.modules.synologychat.synologychat import SynologyChat
|
||||||
from app.schemas import MessageChannel, CommingMessage, Notification
|
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]):
|
class SynologyChatModule(_ModuleBase, _MessageBase[SynologyChat]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.Notifications.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -2,18 +2,20 @@ import copy
|
|||||||
import json
|
import json
|
||||||
from typing import Optional, Union, List, Tuple, Any, Dict
|
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.context import MediaInfo, Context
|
||||||
from app.core.event import eventmanager
|
from app.core.event import eventmanager
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _ModuleBase, _MessageBase
|
from app.modules import _ModuleBase, _MessageBase
|
||||||
from app.modules.telegram.telegram import Telegram
|
from app.modules.telegram.telegram import Telegram
|
||||||
from app.schemas import MessageChannel, CommingMessage, Notification, CommandRegisterEventData
|
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
|
from app.utils.structures import DictUtils
|
||||||
|
|
||||||
|
|
||||||
class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
|
class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.Notifications.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -5,18 +5,19 @@ from torrentool.torrent import Torrent
|
|||||||
from transmission_rpc import File
|
from transmission_rpc import File
|
||||||
|
|
||||||
from app import schemas
|
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.core.metainfo import MetaInfo
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _ModuleBase, _DownloaderBase
|
from app.modules import _ModuleBase, _DownloaderBase
|
||||||
from app.modules.transmission.transmission import Transmission
|
from app.modules.transmission.transmission import Transmission
|
||||||
from app.schemas import TransferTorrent, DownloadingTorrent
|
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
|
from app.utils.string import StringUtils
|
||||||
|
|
||||||
|
|
||||||
class TransmissionModule(_ModuleBase, _DownloaderBase[Transmission]):
|
class TransmissionModule(_ModuleBase, _DownloaderBase[Transmission]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.Downloaders.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
from typing import Any, Generator, List, Optional, Tuple, Union
|
from typing import Any, Generator, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
|
from app.core.config import on_config_change
|
||||||
from app.core.context import MediaInfo
|
from app.core.context import MediaInfo
|
||||||
from app.core.event import eventmanager
|
from app.core.event import eventmanager
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _MediaServerBase, _ModuleBase
|
from app.modules import _MediaServerBase, _ModuleBase
|
||||||
from app.modules.trimemedia.trimemedia import TrimeMedia
|
from app.modules.trimemedia.trimemedia import TrimeMedia
|
||||||
from app.schemas import AuthCredentials, AuthInterceptCredentials
|
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]):
|
class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.MediaServers.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Optional, Union, List, Tuple, Any, Dict
|
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.context import Context, MediaInfo
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _ModuleBase, _MessageBase
|
from app.modules import _ModuleBase, _MessageBase
|
||||||
from app.modules.vocechat.vocechat import VoceChat
|
from app.modules.vocechat.vocechat import VoceChat
|
||||||
from app.schemas import MessageChannel, CommingMessage, Notification
|
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]):
|
class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.Notifications.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ from typing import Union, Tuple
|
|||||||
|
|
||||||
from pywebpush import webpush, WebPushException
|
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.log import logger
|
||||||
from app.modules import _ModuleBase, _MessageBase
|
from app.modules import _ModuleBase, _MessageBase
|
||||||
from app.schemas import Notification
|
from app.schemas import Notification
|
||||||
from app.schemas.types import ModuleType, MessageChannel
|
from app.schemas.types import ModuleType, MessageChannel, SystemConfigKey
|
||||||
|
|
||||||
|
|
||||||
class WebPushModule(_ModuleBase, _MessageBase):
|
class WebPushModule(_ModuleBase, _MessageBase):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.Notifications.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import copy
|
|||||||
import xml.dom.minidom
|
import xml.dom.minidom
|
||||||
from typing import Optional, Union, List, Tuple, Any, Dict
|
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.context import Context, MediaInfo
|
||||||
from app.core.event import eventmanager
|
from app.core.event import eventmanager
|
||||||
from app.log import logger
|
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.WXBizMsgCrypt3 import WXBizMsgCrypt
|
||||||
from app.modules.wechat.wechat import WeChat
|
from app.modules.wechat.wechat import WeChat
|
||||||
from app.schemas import MessageChannel, CommingMessage, Notification, CommandRegisterEventData
|
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.dom import DomUtils
|
||||||
from app.utils.structures import DictUtils
|
from app.utils.structures import DictUtils
|
||||||
|
|
||||||
|
|
||||||
class WechatModule(_ModuleBase, _MessageBase[WeChat]):
|
class WechatModule(_ModuleBase, _MessageBase[WeChat]):
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.Notifications.value])
|
||||||
def init_module(self) -> None:
|
def init_module(self) -> None:
|
||||||
"""
|
"""
|
||||||
初始化模块
|
初始化模块
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ from watchdog.observers.polling import PollingObserver
|
|||||||
from app.chain import ChainBase
|
from app.chain import ChainBase
|
||||||
from app.chain.storage import StorageChain
|
from app.chain.storage import StorageChain
|
||||||
from app.chain.transfer import TransferChain
|
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.db.systemconfig_oper import SystemConfigOper
|
||||||
from app.helper.directory import DirectoryHelper
|
from app.helper.directory import DirectoryHelper
|
||||||
from app.helper.message import MessageHelper
|
from app.helper.message import MessageHelper
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.schemas import FileItem
|
from app.schemas import FileItem
|
||||||
|
from app.schemas.types import SystemConfigKey
|
||||||
from app.utils.singleton import Singleton
|
from app.utils.singleton import Singleton
|
||||||
|
|
||||||
lock = Lock()
|
lock = Lock()
|
||||||
@@ -85,6 +86,7 @@ class Monitor(metaclass=Singleton):
|
|||||||
# 启动目录监控和文件整理
|
# 启动目录监控和文件整理
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
|
@on_config_change([SystemConfigKey.Directories.value])
|
||||||
def init(self):
|
def init(self):
|
||||||
"""
|
"""
|
||||||
启动监控
|
启动监控
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from app.chain.subscribe import SubscribeChain
|
|||||||
from app.chain.tmdb import TmdbChain
|
from app.chain.tmdb import TmdbChain
|
||||||
from app.chain.transfer import TransferChain
|
from app.chain.transfer import TransferChain
|
||||||
from app.chain.workflow import WorkflowChain
|
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.event import EventManager
|
||||||
from app.core.plugin import PluginManager
|
from app.core.plugin import PluginManager
|
||||||
from app.db.systemconfig_oper import SystemConfigOper
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
@@ -57,6 +57,8 @@ class Scheduler(metaclass=Singleton):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
|
@on_config_change(['DEV', 'COOKIECLOUD_INTERVAL', 'MEDIASERVER_SYNC_INTERVAL', 'SUBSCRIBE_SEARCH',
|
||||||
|
'SUBSCRIBE_MODE', 'SUBSCRIBE_RSS_INTERVAL', 'SITEDATA_REFRESH_INTERVAL'])
|
||||||
def init(self):
|
def init(self):
|
||||||
"""
|
"""
|
||||||
初始化定时服务
|
初始化定时服务
|
||||||
|
|||||||
Reference in New Issue
Block a user