mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-08 14:43:34 +08:00
fix: retry qbittorrent files after add
This commit is contained in:
@@ -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, "获取种子文件失败,下载任务可能在暂停状态"
|
||||
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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():
|
||||
"""
|
||||
设置全局限速时允许只传一个方向,未传方向按不限速处理。
|
||||
|
||||
Reference in New Issue
Block a user