refactor: expand runtime contracts and debt ratchets

This commit is contained in:
jxxghp
2026-08-22 08:25:36 +08:00
parent 820f7e8850
commit 5d9c6c8b19
37 changed files with 883 additions and 339 deletions
+1
View File
@@ -52,6 +52,7 @@ class ChainBase(RecognitionMixin, MessageProcessingMixin, NotificationMixin,
self.pluginmanager = context.plugin_manager
self.filecache = context.file_cache
self.async_filecache = context.async_file_cache
self.runtime_config = context.configuration
self.data_ports = context.data_ports or get_chain_data_ports()
self.durable_event_writer = context.durable_event_writer
self._module_dispatcher = context.module_dispatcher_factory(
+81 -67
View File
@@ -1038,6 +1038,51 @@ class DownloadChain(ChainBase):
# 返回 种子文件路径,种子目录名,种子文件清单
return content, download_folder, files
@staticmethod
def _apply_resource_download_event(
context: Context,
episodes: Optional[Set[int]],
channel: Optional[NotificationChannel],
source: Optional[str],
downloader: Optional[str],
save_path: Optional[str],
userid: Union[str, int, None],
username: Optional[str],
) -> tuple[Optional[str], Optional[str]]:
"""应用资源下载事件覆盖,并校验事件返回的下载目录。"""
event_data = ResourceDownloadEventData(
context=context,
episodes=episodes or context.meta_info.episode_list,
channel=channel,
origin=source,
downloader=downloader,
options={
"save_path": save_path,
"userid": userid,
"username": username,
"media_category": context.media_info.category,
},
)
event = eventmanager.send_event(ChainEventType.ResourceDownload, event_data)
if event and event.event_data:
event_data = event.event_data
if event_data.cancel:
logger.debug(
"Resource download canceled by event: %s,Reason: %s",
event_data.source,
event_data.reason,
)
return save_path, "下载被事件取消"
if event_data.options and "save_path" in event_data.options:
save_path = event_data.options.get("save_path")
if save_path is None:
return None, None
try:
return validate_download_save_path(save_path), None
except ValueError as err:
logger.warn(str(err))
return save_path, str(err)
def download_single(self, context: Context,
torrent_file: Path = None,
torrent_content: Optional[Union[str, bytes]] = None,
@@ -1077,40 +1122,12 @@ class DownloadChain(ChainBase):
_media = MediaChain().supplement_tmdb_info(_media, _meta)
context.media_info = _media
# 发送资源下载事件,允许外部拦截下载
event_data = ResourceDownloadEventData(
context=context,
episodes=episodes or context.meta_info.episode_list,
channel=channel,
origin=source,
downloader=downloader,
options={
"save_path": save_path,
"userid": userid,
"username": username,
"media_category": _media.category
}
save_path, event_error = self._apply_resource_download_event(
context, episodes, channel, source, downloader, save_path,
userid, username,
)
# 触发资源下载事件
event = eventmanager.send_event(ChainEventType.ResourceDownload, event_data)
if event and event.event_data:
event_data: ResourceDownloadEventData = event.event_data
# 如果事件被取消,跳过资源下载
if event_data.cancel:
logger.debug(
f"Resource download canceled by event: {event_data.source},"
f"Reason: {event_data.reason}")
return (None, "下载被事件取消") if return_detail else None
# 如果事件修改了下载路径,使用新路径
if event_data.options and "save_path" in event_data.options:
save_path = event_data.options.get("save_path")
if save_path is not None:
try:
save_path = validate_download_save_path(save_path)
except ValueError as err:
logger.warn(str(err))
return (None, str(err)) if return_detail else None
if event_error:
return (None, event_error) if return_detail else None
# 实际下载的集数
download_episodes = episode_rules.format_ranges(list(episodes)) if episodes else None
@@ -1913,34 +1930,6 @@ class DownloadChain(ChainBase):
logger.error("电视剧缺集检查需要有效的 media_source 和 media_id")
return False, no_exists or {}
def __append_no_exists(_season: int, _episodes: list, _total: int, _start: int):
"""
添加不存在的季集信息
{source:id: [
"season": int,
"episodes": list,
"total_episode": int,
"start_episode": int
]}
"""
mediakey = build_media_key(media_source, media_id)
if not no_exists.get(mediakey):
no_exists[mediakey] = {
_season: NotExistMediaInfo(
season=_season,
episodes=_episodes,
total_episode=_total,
start_episode=_start
)
}
else:
no_exists[mediakey][_season] = NotExistMediaInfo(
season=_season,
episodes=_episodes,
total_episode=_total,
start_episode=_start
)
if not no_exists:
no_exists = {}
@@ -2006,8 +1995,10 @@ class DownloadChain(ChainBase):
continue
# 总集数
total_ep = totals.get(season) or len(episodes)
__append_no_exists(_season=season, _episodes=[],
_total=total_ep, _start=min(episodes))
self._append_no_exists(
no_exists, media_source, media_id, season, [],
total_ep, min(episodes)
)
return False, no_exists
else:
# 存在一些,检查每季缺失的季集
@@ -2035,12 +2026,16 @@ class DownloadChain(ChainBase):
# 全部集存在
continue
# 添加不存在的季集信息
__append_no_exists(_season=season, _episodes=lack_episodes,
_total=season_total, _start=min(lack_episodes))
self._append_no_exists(
no_exists, media_source, media_id, season,
lack_episodes, season_total, min(lack_episodes)
)
else:
# 全季不存在
__append_no_exists(_season=season, _episodes=[],
_total=season_total, _start=min(episodes))
self._append_no_exists(
no_exists, media_source, media_id, season, [],
season_total, min(episodes)
)
# 存在不完整的剧集
if no_exists:
logger.debug(f"媒体库中已存在部分剧集,缺失:{no_exists}")
@@ -2048,6 +2043,25 @@ class DownloadChain(ChainBase):
# 全部存在
return True, no_exists
@staticmethod
def _append_no_exists(
no_exists: Dict[str, Dict[int, NotExistMediaInfo]],
media_source: Optional[MediaSource],
media_id: Optional[str],
season: int,
episodes: list,
total: int,
start: int,
) -> None:
"""把一季缺失信息合并到标准媒体身份对应的结果中。"""
media_key = build_media_key(media_source, media_id)
no_exists.setdefault(media_key, {})[season] = NotExistMediaInfo(
season=season,
episodes=episodes,
total_episode=total,
start_episode=start,
)
def remote_downloading(self, channel: NotificationChannel, userid: Union[str, int] = None, source: Optional[str] = None):
"""
查询正在下载的任务,并发送消息
+1 -2
View File
@@ -3,7 +3,6 @@ from typing import Any, Optional, List, Dict
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.chain import ChainBase
from app.runtime.config import settings
from app.application.directory import DirectoryHelper
from app.runtime.log import logger
@@ -148,7 +147,7 @@ class StorageChain(ChainBase):
"""
删除媒体文件,以及不含媒体文件的目录
"""
media_exts = settings.RMT_MEDIAEXT + settings.DOWNLOAD_TMPEXT + settings.RMT_SUBEXT + settings.RMT_AUDIOEXT
media_exts = self.runtime_config.media_extensions
fileitem_path = Path(fileitem.path) if fileitem.path else Path("")
if len(fileitem_path.parts) <= 2:
logger.warn(f"{fileitem.storage}{fileitem.path} 根目录或一级目录不允许删除")