fix(workflow): 修复添加下载的媒体上下文类型 #6931

This commit is contained in:
jxxghp
2026-10-09 21:33:06 +08:00
parent ec93101289
commit 0115c41459
2 changed files with 118 additions and 5 deletions

View File

@@ -1,12 +1,20 @@
from dataclasses import fields
from typing import Optional
from pydantic import Field
from app.chain.download import DownloadChain
from app.chain.media import MediaChain
from app.domain.context import Context as DownloadContext
from app.domain.context import MediaInfo as DownloadMediaInfo
from app.domain.context import MusicInfo as DownloadMusicInfo
from app.domain.context import TorrentInfo as DownloadTorrentInfo
from app.domain.meta.metabase import MetaBase
from app.domain.meta.metamusic import MetaMusic
from app.domain.metainfo import MetaInfo
from app.runtime.log import logger
from app.runtime.stop import runtime_stop_state
from app.schemas.context import Context as WorkflowTorrentContext
from app.schemas.types import MediaType
from app.schemas.workflow import ActionContext, ActionParams, DownloadTask
from app.workflow.actions import BaseAction
@@ -34,6 +42,7 @@ class AddDownloadAction(BaseAction):
}
def __init__(self, action_id: str):
"""初始化下载结果集合和动作错误状态。"""
super().__init__(action_id)
self._added_downloads = []
self._has_error = False
@@ -44,11 +53,12 @@ class AddDownloadAction(BaseAction):
@property
def success(self) -> bool:
"""返回本次动作是否未遇到下载失败。"""
return not self._has_error
def execute(self, workflow_id: int, params: dict, context: ActionContext) -> ActionContext:
"""
将上下文中的torrents添加到下载任务中
将工作流资源还原为下载链所需的领域上下文并添加下载任务。
"""
params = AddDownloadParams(**params)
_started = False
@@ -71,10 +81,11 @@ class AddDownloadAction(BaseAction):
self._has_error = True
logger.warning(f"{t.torrent_info.title} 未识别到媒体信息,无法下载")
continue
download_context = self._to_domain_context(t)
if params.only_lack:
exists_info = DownloadChain().media_exists(t.media_info)
exists_info = DownloadChain().media_exists(download_context.media_info)
if exists_info:
if t.media_info.type == MediaType.MOVIE:
if download_context.media_info.type == MediaType.MOVIE:
# 电影
logger.warning(f"{t.torrent_info.title} 媒体库中已存在,跳过")
continue
@@ -99,7 +110,7 @@ class AddDownloadAction(BaseAction):
continue
_started = True
did = DownloadChain().download_single(context=t,
did = DownloadChain().download_single(context=download_context,
downloader=params.downloader,
save_path=params.save_path,
label=params.labels)
@@ -118,3 +129,67 @@ class AddDownloadAction(BaseAction):
self.job_done(f"已添加 {len(self._added_downloads)} 个下载任务")
return context
@staticmethod
def _to_domain_context(context: WorkflowTorrentContext) -> DownloadContext:
"""把工作流传输模型转换为下载链使用的领域上下文和媒体类型。"""
if context.media_info is None or context.torrent_info is None:
raise ValueError("工作流下载上下文缺少媒体或种子信息")
media_info = context.media_info
if isinstance(media_info, (DownloadMediaInfo, DownloadMusicInfo)):
domain_media_info = media_info
else:
media_data = media_info.model_dump()
if media_data.get("type") == MediaType.MUSIC.value:
domain_media_info = DownloadMusicInfo.from_dict(media_data)
else:
domain_media_info = DownloadMediaInfo()
domain_media_info.from_dict(media_data)
torrent_info = context.torrent_info
if isinstance(torrent_info, DownloadTorrentInfo):
domain_torrent_info = torrent_info
else:
domain_torrent_info = DownloadTorrentInfo()
domain_torrent_info.from_dict(torrent_info.model_dump())
meta_info = context.meta_info
if isinstance(meta_info, MetaBase):
domain_meta_info = meta_info
elif meta_info is None:
domain_meta_info = None
else:
meta_data = meta_info.model_dump()
meta_type = meta_data.get("type")
if meta_type == MediaType.MUSIC.value:
domain_meta_info = MetaMusic.from_dict(meta_data)
else:
domain_meta_info = MetaInfo(
title=meta_data.get("org_string") or meta_data.get("title") or torrent_info.title or "",
subtitle=meta_data.get("subtitle"),
mtype=MediaType(meta_type) if meta_type else None,
)
for item in fields(domain_meta_info):
if item.name not in meta_data:
continue
value = meta_data[item.name]
if item.name == "type" and value:
value = MediaType(value)
setattr(domain_meta_info, item.name, value)
if meta_data.get("name"):
domain_meta_info.name = meta_data["name"]
return DownloadContext(
meta_info=domain_meta_info,
media_info=domain_media_info,
torrent_info=domain_torrent_info,
resource_source=context.resource_source or "unknown",
match_source=context.match_source or "unknown",
candidate_recognized=bool(context.candidate_recognized),
media_info_is_target=bool(context.media_info_is_target),
match_status=context.match_status,
match_reason=context.match_reason,
confirmed_full_coverage=bool(context.confirmed_full_coverage),
music_track_keys=list(context.music_track_keys) if context.music_track_keys else None,
)

View File

@@ -252,8 +252,10 @@ def test_workflow_manager_list_actions_exposes_contract():
def test_add_download_only_lack_handles_schema_metainfo(monkeypatch):
"""添加下载动作开启仅下载缺失资源时应支持 Schema MetaInfo 的 season_list 属性并正确过滤已存在剧集。"""
"""仅下载缺失资源时应兼容工作流 MetaInfo 并向下载链传递领域上下文。"""
import app.workflow.actions.add_download as add_download_module
from app.domain.context import MediaInfo as DomainMediaInfo
from app.domain.meta.metabase import MetaBase
from app.schemas.context import (
Context as SchemaContext,
)
@@ -272,11 +274,21 @@ def test_add_download_only_lack_handles_schema_metainfo(monkeypatch):
downloaded = []
class FakeDownloadChain:
"""模拟媒体库检查和下载提交,并验证工作流对象已转换为领域类型。"""
def media_exists(self, mediainfo):
"""返回已存在剧集,并检查传入媒体使用领域类型。"""
assert isinstance(mediainfo, DomainMediaInfo)
assert mediainfo.type is MediaType.TV
# 模拟媒体库已存在第 1 季的第 1、2 集
return SimpleNamespace(seasons={1: [1, 2]})
def download_single(self, context=None, **kwargs):
"""记录提交的种子名称,并验证下载链拿到领域上下文。"""
assert isinstance(context, Context)
assert isinstance(context.media_info, DomainMediaInfo)
assert isinstance(context.meta_info, MetaBase)
assert isinstance(context.torrent_info, TorrentInfo)
downloaded.append(context.torrent_info.title)
return f"hash-{context.torrent_info.title}"
@@ -325,3 +337,29 @@ def test_add_download_only_lack_handles_schema_metainfo(monkeypatch):
assert downloaded == ["Show S01E03", "Show Ep04"]
assert len(result.downloads) == 2
assert [d.download_id for d in result.downloads] == ["hash-Show S01E03", "hash-Show Ep04"]
def test_add_download_converts_workflow_music_context_to_domain_types():
"""添加下载动作应将音乐工作流模型恢复为下载链使用的领域对象。"""
from app.domain.context import MusicInfo as DomainMusicInfo
from app.domain.meta.metamusic import MetaMusic
from app.schemas.context import Context as SchemaContext
from app.schemas.context import TorrentInfo as SchemaTorrentInfo
from app.schemas.music import MusicInfo as SchemaMusicInfo
from app.schemas.music import MusicMeta
from app.schemas.types import MediaType
from app.workflow.actions.add_download import AddDownloadAction
context = SchemaContext(
meta_info=MusicMeta(title="Track", artists=["Artist"]),
media_info=SchemaMusicInfo(title="Track", artists=["Artist"]),
torrent_info=SchemaTorrentInfo(title="Track", site=1),
)
result = AddDownloadAction._to_domain_context(context)
assert isinstance(result, Context)
assert isinstance(result.meta_info, MetaMusic)
assert isinstance(result.media_info, DomainMusicInfo)
assert result.media_info.type is MediaType.MUSIC
assert isinstance(result.torrent_info, TorrentInfo)