fix: 订阅下载失败时尝试后续候选

This commit is contained in:
jxxghp
2026-06-01 18:46:50 +08:00
parent df732731d9
commit d353e7b208
2 changed files with 111 additions and 4 deletions

View File

@@ -562,6 +562,12 @@ class DownloadChain(ChainBase):
return set()
return set(_context.located_episodes)
def __get_movie_download_key(_context: Context) -> str:
"""
获取电影下载去重键,确保失败候选不会阻断后续同名资源尝试。
"""
return _context.media_info.title_year
# 发送资源选择事件,允许外部修改上下文数据
logger.debug(f"Initial contexts: {len(contexts)} items, Downloader: {downloader}")
event_data = ResourceSelectionEventData(
@@ -578,14 +584,18 @@ class DownloadChain(ChainBase):
f"{len(event_data.updated_contexts)} items (source: {event_data.source})")
contexts = event_data.updated_contexts
# 分组排序
contexts = TorrentHelper().sort_group_torrents(contexts)
# 仅排序,不提前按媒体控重;下载失败时需要继续尝试同组后续候选。
contexts = TorrentHelper().sort_torrents(contexts)
# 如果是电影,直接下载
downloaded_movies = set()
for context in contexts:
if global_vars.is_system_stopped:
break
if context.media_info.type == MediaType.MOVIE:
movie_key = __get_movie_download_key(context)
if movie_key in downloaded_movies:
continue
logger.info(f"开始下载电影 {context.torrent_info.title} ...")
if self.download_single(context, save_path=save_path, channel=channel,
source=source, userid=userid, username=username,
@@ -593,6 +603,7 @@ class DownloadChain(ChainBase):
# 下载成功
logger.info(f"{context.torrent_info.title} 添加下载成功")
downloaded_list.append(context)
downloaded_movies.add(movie_key)
# 电视剧整季匹配
if no_exists:

View File

@@ -105,9 +105,31 @@ class _FakeBatchTorrentHelper:
episodes = []
def sort_group_torrents(self, contexts):
def sort_torrents(self, contexts):
"""
保持测试输入顺序,避免依赖真实站点优先级配置。
"""
return contexts
def sort_group_torrents(self, contexts):
"""
模拟真实提前控重行为,回归时会丢掉同一媒体季集的后续候选。
"""
results = []
added = set()
for context in contexts:
media = context.media_info
meta = context.meta_info
if media.type == MediaType.TV:
media_name = f"{media.title_year}{meta.season_episode}"
else:
media_name = media.title_year
if media_name in added:
continue
added.add(media_name)
results.append(context)
return results
def get_torrent_episodes(self, _files):
return list(self.episodes)
@@ -118,9 +140,15 @@ def _build_tv_context(episode_list=None):
"""
episodes = episode_list or []
return SimpleNamespace(
media_info=SimpleNamespace(type=MediaType.TV, tmdb_id=1, douban_id=None),
media_info=SimpleNamespace(
type=MediaType.TV,
title_year="Test Show (2026)",
tmdb_id=1,
douban_id=None,
),
meta_info=SimpleNamespace(
season_list=[1],
season_episode="S01E01",
episode_list=episodes,
title="Test Show",
org_string="Test Show S01 2160p",
@@ -164,6 +192,74 @@ def test_batch_download_rejects_complete_coverage_when_files_do_not_cover_target
chain.download_single.assert_not_called()
def test_batch_download_tries_next_episode_candidate_when_first_download_fails(monkeypatch):
"""
同一季集的首个候选下载失败时,应继续尝试排序后的下一个候选资源。
"""
_FakeBatchTorrentHelper.episodes = []
monkeypatch.setattr(download_module, "TorrentHelper", _FakeBatchTorrentHelper)
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
chain = DownloadChain.__new__(DownloadChain)
chain.download_single = MagicMock(side_effect=[None, "hash"])
first_context = _build_tv_context(episode_list=[1])
first_context.torrent_info.title = "Test Show S01E01 First"
second_context = _build_tv_context(episode_list=[1])
second_context.torrent_info.title = "Test Show S01E01 Second"
no_exists = {
1: {
1: NotExistMediaInfo(
season=1,
episodes=[1],
total_episode=1,
start_episode=1,
)
}
}
downloads, lefts = chain.batch_download(
contexts=[first_context, second_context],
no_exists=no_exists,
)
assert downloads == [second_context]
assert lefts == {}
assert chain.download_single.call_count == 2
assert chain.download_single.call_args_list[0].args[0] is first_context
assert chain.download_single.call_args_list[1].args[0] is second_context
def test_batch_download_does_not_download_duplicate_movie_after_success(monkeypatch):
"""
电影保留失败重试能力,但同一影片成功一次后不应继续添加后续候选。
"""
_FakeBatchTorrentHelper.episodes = []
monkeypatch.setattr(download_module, "TorrentHelper", _FakeBatchTorrentHelper)
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
chain = DownloadChain.__new__(DownloadChain)
chain.download_single = MagicMock(return_value="hash")
first_context = SimpleNamespace(
media_info=SimpleNamespace(type=MediaType.MOVIE, title_year="Demo Movie (2026)"),
meta_info=SimpleNamespace(season_episode=""),
torrent_info=SimpleNamespace(title="Demo Movie First"),
)
second_context = SimpleNamespace(
media_info=SimpleNamespace(type=MediaType.MOVIE, title_year="Demo Movie (2026)"),
meta_info=SimpleNamespace(season_episode=""),
torrent_info=SimpleNamespace(title="Demo Movie Second"),
)
downloads, lefts = chain.batch_download(contexts=[first_context, second_context])
assert downloads == [first_context]
assert lefts is None
chain.download_single.assert_called_once()
assert chain.download_single.call_args.args[0] is first_context
def test_batch_download_accepts_complete_coverage_when_files_cover_target_range(monkeypatch):
"""
自定义起始集场景按目标范围覆盖判断100-143 可满足 start=100、total=143。