mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-03-20 12:08:09 +08:00
Merge pull request #3201 from InfinityPacer/feature/event
This commit is contained in:
@@ -84,7 +84,6 @@ class EventManager(metaclass=Singleton):
|
||||
self.__disabled_handlers = set() # 禁用的事件处理器集合
|
||||
self.__disabled_classes = set() # 禁用的事件处理器类集合
|
||||
self.__lock = threading.Lock() # 线程锁
|
||||
self.__processing_events = {} # 用于记录当前正在处理的事件 {event_hash: event}
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
@@ -130,14 +129,6 @@ class EventManager(metaclass=Singleton):
|
||||
for handler in handlers.values()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def __get_event_hash(event: Event) -> str:
|
||||
"""
|
||||
计算事件的唯一标识符(hash)
|
||||
"""
|
||||
data_string = str(event.event_type.value) + str(event.event_data)
|
||||
return str(uuid.uuid5(uuid.NAMESPACE_DNS, data_string))
|
||||
|
||||
def send_event(self, etype: Union[EventType, ChainEventType], data: Optional[Union[Dict, ChainEventData]] = None,
|
||||
priority: int = DEFAULT_EVENT_PRIORITY) -> Optional[Event]:
|
||||
"""
|
||||
@@ -148,12 +139,6 @@ class EventManager(metaclass=Singleton):
|
||||
:return: 如果是链式事件,返回处理后的事件数据;否则返回 None
|
||||
"""
|
||||
event = Event(etype, data, priority)
|
||||
event_hash = self.__get_event_hash(event)
|
||||
with self.__lock:
|
||||
if event_hash in self.__processing_events:
|
||||
logger.debug(f"Duplicate event ignored: {event}")
|
||||
return None
|
||||
self.__processing_events[event_hash] = event
|
||||
if isinstance(etype, EventType):
|
||||
self.__trigger_broadcast_event(event)
|
||||
elif isinstance(etype, ChainEventType):
|
||||
@@ -335,14 +320,9 @@ class EventManager(metaclass=Singleton):
|
||||
"""
|
||||
触发链式事件,按顺序调用订阅的处理器,并记录处理耗时
|
||||
"""
|
||||
try:
|
||||
logger.debug(f"Triggering synchronous chain event: {event}")
|
||||
dispatch = self.__dispatch_chain_event(event)
|
||||
return event if dispatch else None
|
||||
finally:
|
||||
event_hash = self.__get_event_hash(event)
|
||||
with self.__lock:
|
||||
self.__processing_events.pop(event_hash, None)
|
||||
logger.debug(f"Triggering synchronous chain event: {event}")
|
||||
dispatch = self.__dispatch_chain_event(event)
|
||||
return event if dispatch else None
|
||||
|
||||
def __trigger_broadcast_event(self, event: Event):
|
||||
"""
|
||||
@@ -383,9 +363,6 @@ class EventManager(metaclass=Singleton):
|
||||
return
|
||||
for handler_id, handler in handlers.items():
|
||||
self.__executor.submit(self.__safe_invoke_handler, handler, event)
|
||||
event_hash = self.__get_event_hash(event)
|
||||
with self.__lock:
|
||||
self.__processing_events.pop(event_hash, None)
|
||||
|
||||
def __safe_invoke_handler(self, handler: Callable, event: Event):
|
||||
"""
|
||||
|
||||
@@ -7,6 +7,7 @@ from jinja2 import Template
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.context import MediaInfo
|
||||
from app.core.event import eventmanager
|
||||
from app.core.meta import MetaBase
|
||||
from app.core.metainfo import MetaInfo, MetaInfoPath
|
||||
from app.helper.directory import DirectoryHelper
|
||||
@@ -16,7 +17,8 @@ from app.log import logger
|
||||
from app.modules import _ModuleBase
|
||||
from app.modules.filemanager.storages import StorageBase
|
||||
from app.schemas import TransferInfo, ExistMediaInfo, TmdbEpisode, TransferDirectoryConf, FileItem, StorageUsage
|
||||
from app.schemas.types import MediaType, ModuleType
|
||||
from app.schemas.event import SmartRenameEventData
|
||||
from app.schemas.types import MediaType, ModuleType, ChainEventType
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
lock = Lock()
|
||||
@@ -130,7 +132,6 @@ class FileManagerModule(_ModuleBase):
|
||||
)
|
||||
return str(path)
|
||||
|
||||
|
||||
def save_config(self, storage: str, conf: Dict) -> None:
|
||||
"""
|
||||
保存存储配置
|
||||
@@ -1146,18 +1147,46 @@ class FileManagerModule(_ModuleBase):
|
||||
# 文件后缀
|
||||
"fileExt": file_ext,
|
||||
# 自定义占位符
|
||||
"customization": meta.customization
|
||||
"customization": meta.customization,
|
||||
# 文件元数据
|
||||
"__meta__": meta,
|
||||
# 识别的媒体信息
|
||||
"__mediainfo__": mediainfo,
|
||||
# 当前季的全部集信息
|
||||
"__episodes_info__": episodes_info,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_rename_path(template_string: str, rename_dict: dict, path: Path = None) -> Path:
|
||||
"""
|
||||
生成重命名后的完整路径
|
||||
生成重命名后的完整路径,支持智能重命名事件
|
||||
:param template_string: Jinja2 模板字符串
|
||||
:param rename_dict: 渲染上下文,用于替换模板中的变量
|
||||
:param path: 可选的基础路径,如果提供,将在其基础上拼接生成的路径
|
||||
:return: 生成的完整路径
|
||||
"""
|
||||
# 创建jinja2模板对象
|
||||
template = Template(template_string)
|
||||
# 渲染生成的字符串
|
||||
render_str = template.render(rename_dict)
|
||||
|
||||
logger.debug(f"Initial render string: {render_str}")
|
||||
# 发送智能重命名事件
|
||||
event_data = SmartRenameEventData(
|
||||
template_string=template_string,
|
||||
rename_dict=rename_dict,
|
||||
render_str=render_str,
|
||||
path=path
|
||||
)
|
||||
event = eventmanager.send_event(ChainEventType.SmartRename, event_data)
|
||||
# 检查事件返回的结果
|
||||
if event and event.event_data:
|
||||
event_data: SmartRenameEventData = event.event_data
|
||||
if event_data.updated and event_data.updated_str:
|
||||
logger.debug(f"Render string updated by event: "
|
||||
f"{render_str} -> {event_data.updated_str} (source: {event_data.source})")
|
||||
render_str = event_data.updated_str
|
||||
|
||||
# 目的路径
|
||||
if path:
|
||||
return path / render_str
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Optional, Dict
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from pydantic import BaseModel, Field, root_validator
|
||||
|
||||
@@ -114,3 +115,31 @@ class CommandRegisterEventData(ChainEventData):
|
||||
# 输出参数
|
||||
cancel: bool = Field(False, description="是否取消注册")
|
||||
source: str = Field("未知拦截源", description="拦截源")
|
||||
|
||||
|
||||
class SmartRenameEventData(ChainEventData):
|
||||
"""
|
||||
SmartRename 事件的数据模型
|
||||
|
||||
Attributes:
|
||||
# 输入参数
|
||||
template_string (str): Jinja2 模板字符串
|
||||
rename_dict (dict): 渲染上下文
|
||||
render_str (str): 渲染生成的字符串
|
||||
path (Optional[Path]): 当前文件的目标路径
|
||||
|
||||
# 输出参数
|
||||
updated (bool): 是否已更新,默认值为 False
|
||||
updated_str (str): 更新后的字符串
|
||||
source (str): 拦截源,默认值为 "未知拦截源"
|
||||
"""
|
||||
# 输入参数
|
||||
template_string: str = Field(..., description="模板字符串")
|
||||
rename_dict: Dict[str, Any] = Field(..., description="渲染上下文")
|
||||
path: Optional[Path] = Field(None, description="文件的目标路径")
|
||||
render_str: str = Field(..., description="渲染生成的字符串")
|
||||
|
||||
# 输出参数
|
||||
updated: bool = Field(False, description="是否已更新")
|
||||
updated_str: Optional[str] = Field(None, description="更新后的字符串")
|
||||
source: Optional[str] = Field("未知拦截源", description="拦截源")
|
||||
|
||||
@@ -60,14 +60,16 @@ class EventType(Enum):
|
||||
|
||||
# 同步链式事件
|
||||
class ChainEventType(Enum):
|
||||
# 名称识别请求
|
||||
# 名称识别
|
||||
NameRecognize = "name.recognize"
|
||||
# 认证验证请求
|
||||
# 认证验证
|
||||
AuthVerification = "auth.verification"
|
||||
# 认证拦截请求
|
||||
# 认证拦截
|
||||
AuthIntercept = "auth.intercept"
|
||||
# 命令注册请求
|
||||
# 命令注册
|
||||
CommandRegister = "command.register"
|
||||
# 智能重命名
|
||||
SmartRename = "SmartRename"
|
||||
|
||||
|
||||
# 系统配置Key字典
|
||||
|
||||
Reference in New Issue
Block a user