fix: retry qbittorrent files after add

This commit is contained in:
jxxghp
2026-07-05 17:15:07 +08:00
parent b231ad415f
commit 132f27c1c6
3 changed files with 94 additions and 8 deletions

View File

@@ -37,9 +37,14 @@ _QBITTORRENT_PAUSED_STATES = {
"stoppeddl",
"stoppedup",
}
_TORRENT_FILES_RETRY_TIMES = 5
_TORRENT_FILES_RETRY_INTERVAL = 1
class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]):
"""
qBittorrent 下载器模块,负责下载任务添加、文件选择和任务管理。
"""
def init_module(self) -> None:
"""
@@ -50,6 +55,9 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]):
@staticmethod
def get_name() -> str:
"""
获取模块名称
"""
return "Qbittorrent"
@staticmethod
@@ -73,7 +81,10 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]):
"""
return 1
def stop(self):
def stop(self) -> None:
"""
停止模块
"""
pass
def test(self) -> Optional[Tuple[bool, str]]:
@@ -90,6 +101,9 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]):
return True, ""
def init_setting(self) -> Tuple[str, Union[str, bool]]:
"""
返回控制模块启用状态的配置项
"""
pass
def scheduler_job(self) -> None:
@@ -221,7 +235,11 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]):
else:
if is_paused:
# 种子文件
torrent_files = server.get_files(torrent_hash)
torrent_files = server.get_files(
torrent_hash,
retry=_TORRENT_FILES_RETRY_TIMES,
interval=_TORRENT_FILES_RETRY_INTERVAL,
)
if not torrent_files:
return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, "获取种子文件失败,下载任务可能在暂停状态"

View File

@@ -488,17 +488,30 @@ class Qbittorrent:
logger.error(f"删除种子出错:{str(err)}")
return False
def get_files(self, tid: str) -> Optional[TorrentFilesList]:
def get_files(self, tid: str, retry: int = 1, interval: float = 0) -> Optional[TorrentFilesList]:
"""
获取种子文件清单
:param tid: 种子Hash
:param retry: 最多尝试次数
:param interval: 重试间隔,单位秒
:return: 种子文件清单
"""
if not self.qbc:
return None
try:
return self.qbc.torrents_files(torrent_hash=tid)
except Exception as err:
logger.error(f"获取种子文件列表出错:{str(err)}")
return None
last_error = None
retry_times = max(retry, 1)
for index in range(retry_times):
try:
torrent_files = self.qbc.torrents_files(torrent_hash=tid)
if torrent_files:
return torrent_files
except Exception as err:
last_error = err
if index < retry_times - 1 and interval:
time.sleep(interval)
if last_error:
logger.error(f"获取种子文件列表出错:{str(last_error)}")
return None
def set_files(self, **kwargs) -> bool:
"""

View File

@@ -494,6 +494,61 @@ def test_download_falls_back_to_tag_lookup_when_added_ids_missing():
fake_server.get_torrent_id_by_tag.assert_called_once_with(tags="tmp-tag-01")
def test_get_files_retries_until_qbittorrent_files_available():
"""qBittorrent 添加任务后文件列表短暂未就绪时应重试。"""
torrent_files = [{"id": 12, "name": "Show.S01E12.mkv"}]
fake_client = MagicMock()
fake_client.torrents_files.side_effect = [
Exception("Torrent hash(es): abc123"),
torrent_files,
]
with patch.object(Qbittorrent, "_Qbittorrent__login_qbittorrent", return_value=fake_client):
downloader = Qbittorrent(host="http://127.0.0.1", port=8080, username="admin", password="adminadmin")
with patch.object(qbittorrent_module.time, "sleep") as sleep:
result = downloader.get_files("abc123", retry=2, interval=1)
assert result == torrent_files
assert fake_client.torrents_files.call_count == 2
fake_client.torrents_files.assert_called_with(torrent_hash="abc123")
sleep.assert_called_once_with(1)
def test_download_episode_selection_retries_file_list_after_add():
"""按集选择下载时应等待 qBittorrent 刚添加的任务文件列表。"""
class _EpisodeMetaInfo:
"""测试用集数识别对象。"""
def __init__(self, name):
self.episode_list = [12] if "E12" in name else [1]
fake_server = MagicMock()
fake_server.add_torrent.return_value = (True, ["abc123"])
fake_server.get_content_layout.return_value = "Original"
fake_server.get_files.return_value = [
{"id": 1, "name": "Show.S01E01.mkv"},
{"id": 12, "name": "Show.S01E12.mkv"},
]
fake_server.is_force_resume.return_value = False
module = _build_module(fake_server)
with patch.object(qbittorrent_package_module, "MetaInfo", _EpisodeMetaInfo):
result = module.download(
content=b"torrent-content",
download_dir=Path("/downloads"),
cookie="",
episodes={12},
downloader="qb",
)
assert result == ("qb", "abc123", "Original", "添加下载成功,已选择集数:[12]")
fake_server.get_files.assert_called_once_with("abc123", retry=5, interval=1)
fake_server.set_files.assert_called_once_with(torrent_hash="abc123", file_ids=[1], priority=0)
fake_server.start_torrents.assert_called_once_with("abc123")
def test_set_speed_limit_allows_single_direction_limit():
"""
设置全局限速时允许只传一个方向,未传方向按不限速处理。