From 2bb090d2be987132cf08e32e9f56f516b124afef Mon Sep 17 00:00:00 2001 From: jxxghp Date: Mon, 17 Aug 2026 13:24:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(download):=20=E4=B8=8B=E8=BD=BD=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E5=86=B7=E5=8D=B4=E6=97=A5=E5=BF=97=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E5=8E=9F=E5=9B=A0=E5=92=8C=E9=87=8D=E8=AF=95?= =?UTF-8?q?=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 _active_download_failure_fingerprints 返回类型从 Set[str] 改为 Dict[str, DownloadFailure], __is_context_in_failure_cooldown 从失败记录中提取 error_message 和 next_retry_at, 日志输出格式改为「近期添加下载失败(失败原因:xxx),暂时跳过该资源,将于 yyyy-MM-dd HH:mm:ss 后重试」。 --- app/chain/download.py | 37 ++++++++++++++++++++++-------------- tests/test_download_chain.py | 6 +++++- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/app/chain/download.py b/app/chain/download.py index 6811539ca..c15e71f11 100644 --- a/app/chain/download.py +++ b/app/chain/download.py @@ -6,7 +6,7 @@ import re import shutil import time from pathlib import Path -from typing import List, Optional, Tuple, Set, Dict, Union +from typing import TYPE_CHECKING, List, Optional, Tuple, Set, Dict, Union from urllib.parse import parse_qs, urlencode, urljoin, urlparse from app import schemas @@ -44,6 +44,9 @@ from app.foundation import size as size_tools from app.foundation import text as text_tools from app.adapters.system.host import SystemUtils +if TYPE_CHECKING: + from app.db.models.downloadfailure import DownloadFailure + DOWNLOAD_FAILURE_RESOURCE_TTL_SECONDS = 24 * 60 * 60 DOWNLOAD_FAILURE_TRANSIENT_TTL_SECONDS = 60 * 60 @@ -832,12 +835,12 @@ class DownloadChain(ChainBase): self, contexts: List[Context], source: Optional[str], - ) -> Set[str]: + ) -> Dict[str, "DownloadFailure"]: """ - 查询当前订阅候选中仍处于冷却期的失败指纹。 + 查询当前订阅候选中仍处于冷却期的失败记录,返回指纹到失败记录的映射。 """ if not self._is_subscribe_source(source): - return set() + return {} fingerprints = [ fingerprint for fingerprint in [ @@ -847,17 +850,15 @@ class DownloadChain(ChainBase): if fingerprint ] if not fingerprints: - return set() + return {} now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) try: - return set( - DownloadFailureOper() - .get_active_by_fingerprints(fingerprints=fingerprints, now_time=now_time) - .keys() + return DownloadFailureOper().get_active_by_fingerprints( + fingerprints=fingerprints, now_time=now_time, ) except Exception as err: logger.error(f"查询下载失败冷却失败:{str(err)}") - return set() + return {} def download_torrent(self, torrent: TorrentInfo, channel: NotificationChannel = None, @@ -1418,7 +1419,7 @@ class DownloadChain(ChainBase): # 仅排序,不提前按媒体控重;下载失败时需要继续尝试同组后续候选。 contexts = TorrentHelper().sort_torrents(contexts) - active_failure_fingerprints = self._active_download_failure_fingerprints( + active_failure_records = self._active_download_failure_fingerprints( contexts=contexts, source=source, ) @@ -1428,8 +1429,16 @@ class DownloadChain(ChainBase): 判断候选资源是否仍处于失败冷却期。 """ fingerprint = self._build_download_failure_fingerprint(_context) - if fingerprint and fingerprint in active_failure_fingerprints: - logger.info(f"{_context.torrent_info.title} 近期添加下载失败,暂时跳过该资源") + if fingerprint and fingerprint in active_failure_records: + _failure = active_failure_records[fingerprint] + _reason = getattr(_failure, "error_message", None) or "未知原因" + _retry_at = getattr(_failure, "next_retry_at", None) + if _retry_at: + logger.info(f"{_context.torrent_info.title} 近期添加下载失败(失败原因:{_reason})," + f"暂时跳过该资源,将于 {_retry_at} 后重试") + else: + logger.info(f"{_context.torrent_info.title} 近期添加下载失败(失败原因:{_reason})," + f"暂时跳过该资源") return True return False @@ -1439,7 +1448,7 @@ class DownloadChain(ChainBase): """ fingerprint = self._build_download_failure_fingerprint(_context) if fingerprint: - active_failure_fingerprints.add(fingerprint) + active_failure_records[fingerprint] = None # 如果是电影,直接下载 downloaded_movies = set() diff --git a/tests/test_download_chain.py b/tests/test_download_chain.py index a93fafab2..a388166cd 100644 --- a/tests/test_download_chain.py +++ b/tests/test_download_chain.py @@ -921,7 +921,11 @@ def test_batch_download_skips_failed_subscription_resource_and_tries_next(monkey """ assert now_time assert failed_fingerprint in fingerprints - return {failed_fingerprint: SimpleNamespace(fingerprint=failed_fingerprint)} + return {failed_fingerprint: SimpleNamespace( + fingerprint=failed_fingerprint, + error_message="无法读取种子文件", + next_retry_at="2026-01-02 03:04:05", + )} monkeypatch.setattr(download_module, "DownloadFailureOper", _ActiveDownloadFailureOper)