fix(download): 下载失败冷却日志补充失败原因和重试时间

将 _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 后重试」。
This commit is contained in:
jxxghp
2026-08-17 13:24:22 +08:00
parent 3ebbec3317
commit 2bb090d2be
2 changed files with 28 additions and 15 deletions
+23 -14
View File
@@ -6,7 +6,7 @@ import re
import shutil import shutil
import time import time
from pathlib import Path 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 urllib.parse import parse_qs, urlencode, urljoin, urlparse
from app import schemas 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.foundation import text as text_tools
from app.adapters.system.host import SystemUtils 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_RESOURCE_TTL_SECONDS = 24 * 60 * 60
DOWNLOAD_FAILURE_TRANSIENT_TTL_SECONDS = 60 * 60 DOWNLOAD_FAILURE_TRANSIENT_TTL_SECONDS = 60 * 60
@@ -832,12 +835,12 @@ class DownloadChain(ChainBase):
self, self,
contexts: List[Context], contexts: List[Context],
source: Optional[str], source: Optional[str],
) -> Set[str]: ) -> Dict[str, "DownloadFailure"]:
""" """
查询当前订阅候选中仍处于冷却期的失败指纹 查询当前订阅候选中仍处于冷却期的失败记录,返回指纹到失败记录的映射
""" """
if not self._is_subscribe_source(source): if not self._is_subscribe_source(source):
return set() return {}
fingerprints = [ fingerprints = [
fingerprint fingerprint
for fingerprint in [ for fingerprint in [
@@ -847,17 +850,15 @@ class DownloadChain(ChainBase):
if fingerprint if fingerprint
] ]
if not fingerprints: if not fingerprints:
return set() return {}
now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
try: try:
return set( return DownloadFailureOper().get_active_by_fingerprints(
DownloadFailureOper() fingerprints=fingerprints, now_time=now_time,
.get_active_by_fingerprints(fingerprints=fingerprints, now_time=now_time)
.keys()
) )
except Exception as err: except Exception as err:
logger.error(f"查询下载失败冷却失败:{str(err)}") logger.error(f"查询下载失败冷却失败:{str(err)}")
return set() return {}
def download_torrent(self, torrent: TorrentInfo, def download_torrent(self, torrent: TorrentInfo,
channel: NotificationChannel = None, channel: NotificationChannel = None,
@@ -1418,7 +1419,7 @@ class DownloadChain(ChainBase):
# 仅排序,不提前按媒体控重;下载失败时需要继续尝试同组后续候选。 # 仅排序,不提前按媒体控重;下载失败时需要继续尝试同组后续候选。
contexts = TorrentHelper().sort_torrents(contexts) contexts = TorrentHelper().sort_torrents(contexts)
active_failure_fingerprints = self._active_download_failure_fingerprints( active_failure_records = self._active_download_failure_fingerprints(
contexts=contexts, contexts=contexts,
source=source, source=source,
) )
@@ -1428,8 +1429,16 @@ class DownloadChain(ChainBase):
判断候选资源是否仍处于失败冷却期。 判断候选资源是否仍处于失败冷却期。
""" """
fingerprint = self._build_download_failure_fingerprint(_context) fingerprint = self._build_download_failure_fingerprint(_context)
if fingerprint and fingerprint in active_failure_fingerprints: if fingerprint and fingerprint in active_failure_records:
logger.info(f"{_context.torrent_info.title} 近期添加下载失败,暂时跳过该资源") _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 True
return False return False
@@ -1439,7 +1448,7 @@ class DownloadChain(ChainBase):
""" """
fingerprint = self._build_download_failure_fingerprint(_context) fingerprint = self._build_download_failure_fingerprint(_context)
if fingerprint: if fingerprint:
active_failure_fingerprints.add(fingerprint) active_failure_records[fingerprint] = None
# 如果是电影,直接下载 # 如果是电影,直接下载
downloaded_movies = set() downloaded_movies = set()
+5 -1
View File
@@ -921,7 +921,11 @@ def test_batch_download_skips_failed_subscription_resource_and_tries_next(monkey
""" """
assert now_time assert now_time
assert failed_fingerprint in fingerprints 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) monkeypatch.setattr(download_module, "DownloadFailureOper", _ActiveDownloadFailureOper)