mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-07 00:16:57 +08:00
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:
+23
-14
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user