mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-03 05:44:44 +08:00
fix(qbittorrent): verify temporary tag cleanup
This commit is contained in:
@@ -15,6 +15,10 @@ from app.domain import torrent as torrent_rules
|
||||
from app.foundation import url as url_tools
|
||||
|
||||
|
||||
_TORRENT_TAG_CLEANUP_RETRY_TIMES = 10
|
||||
_TORRENT_TAG_CLEANUP_RETRY_INTERVAL = 1
|
||||
|
||||
|
||||
class Qbittorrent:
|
||||
"""
|
||||
qbittorrent下载器
|
||||
@@ -305,6 +309,10 @@ class Qbittorrent:
|
||||
def delete_torrents_tag(self, ids: Optional[Union[str, list]], tag: Union[str, list]) -> bool:
|
||||
"""
|
||||
从指定种子移除标签,并删除全局标签定义
|
||||
|
||||
qBittorrent 5.x 添加任务后可能暂时无法按响应中的种子 ID 查询任务,
|
||||
移除接口此时会静默成功,因此需读取任务标签确认清理结果。
|
||||
|
||||
:param ids: 种子Hash列表;未知种子Hash时传入None,仅删除全局标签定义
|
||||
:param tag: 标签内容
|
||||
:return: 是否删除成功
|
||||
@@ -313,7 +321,41 @@ class Qbittorrent:
|
||||
return False
|
||||
try:
|
||||
if ids:
|
||||
self.qbc.torrents_remove_tags(torrent_hashes=ids, tags=tag)
|
||||
expected_hashes = (
|
||||
{torrent_hash.strip().lower() for torrent_hash in ids.split('|') if torrent_hash.strip()}
|
||||
if isinstance(ids, str)
|
||||
else {str(torrent_hash).strip().lower() for torrent_hash in ids if torrent_hash}
|
||||
)
|
||||
expected_tags = (
|
||||
{tag_name.strip() for tag_name in tag.split(',') if tag_name.strip()}
|
||||
if isinstance(tag, str)
|
||||
else {str(tag_name).strip() for tag_name in tag if tag_name}
|
||||
)
|
||||
for attempt in range(_TORRENT_TAG_CLEANUP_RETRY_TIMES):
|
||||
self.qbc.torrents_remove_tags(torrent_hashes=ids, tags=tag)
|
||||
torrents = self.qbc.torrents_info(torrent_hashes=ids) or []
|
||||
visible_hashes = {
|
||||
str(torrent.get("hash") or "").strip().lower()
|
||||
for torrent in torrents
|
||||
}
|
||||
tags_removed = all(
|
||||
not expected_tags.intersection(
|
||||
{
|
||||
torrent_tag.strip()
|
||||
for torrent_tag in str(torrent.get("tags") or "").split(',')
|
||||
if torrent_tag.strip()
|
||||
}
|
||||
)
|
||||
for torrent in torrents
|
||||
)
|
||||
if expected_hashes.issubset(visible_hashes) and tags_removed:
|
||||
break
|
||||
if attempt < _TORRENT_TAG_CLEANUP_RETRY_TIMES - 1:
|
||||
time.sleep(_TORRENT_TAG_CLEANUP_RETRY_INTERVAL)
|
||||
else:
|
||||
self.qbc.torrents_delete_tags(tags=tag)
|
||||
logger.warning(f"种子 {ids} 的临时Tag未能在重试期限内确认删除:{tag}")
|
||||
return False
|
||||
self.qbc.torrents_delete_tags(tags=tag)
|
||||
return True
|
||||
except Exception as err:
|
||||
|
||||
@@ -733,6 +733,7 @@ def test_download_removes_temporary_tag_from_existing_torrent():
|
||||
def test_delete_torrents_tag_uses_supported_qbittorrent_api_arguments():
|
||||
"""删除标签时应分别调用任务移除接口和全局标签删除接口。"""
|
||||
fake_client = MagicMock()
|
||||
fake_client.torrents_info.return_value = [{"hash": "abc123", "tags": "moviepilot-tag"}]
|
||||
downloader = Qbittorrent.__new__(Qbittorrent)
|
||||
downloader.qbc = fake_client
|
||||
|
||||
@@ -741,6 +742,45 @@ def test_delete_torrents_tag_uses_supported_qbittorrent_api_arguments():
|
||||
torrent_hashes="abc123",
|
||||
tags="tmp-tag-01",
|
||||
)
|
||||
fake_client.torrents_info.assert_called_once_with(torrent_hashes="abc123")
|
||||
fake_client.torrents_delete_tags.assert_called_once_with(tags="tmp-tag-01")
|
||||
|
||||
|
||||
def test_delete_torrents_tag_retries_until_new_torrent_tag_is_removed():
|
||||
"""新添加任务尚未可见或标签状态滞后时应重试并验证删除结果。"""
|
||||
fake_client = MagicMock()
|
||||
fake_client.torrents_info.side_effect = [
|
||||
[],
|
||||
[{"hash": "abc123", "tags": "moviepilot-tag,tmp-tag-01"}],
|
||||
[{"hash": "abc123", "tags": "moviepilot-tag"}],
|
||||
]
|
||||
downloader = Qbittorrent.__new__(Qbittorrent)
|
||||
downloader.qbc = fake_client
|
||||
|
||||
with patch.object(qbittorrent_module.time, "sleep") as sleep:
|
||||
cleaned = downloader.delete_torrents_tag("abc123", "tmp-tag-01")
|
||||
|
||||
assert cleaned
|
||||
assert fake_client.torrents_remove_tags.call_count == 3
|
||||
assert fake_client.torrents_info.call_count == 3
|
||||
assert sleep.call_args_list == [call(1), call(1)]
|
||||
fake_client.torrents_delete_tags.assert_called_once_with(tags="tmp-tag-01")
|
||||
|
||||
|
||||
def test_delete_torrents_tag_reports_unverified_cleanup():
|
||||
"""重试后仍无法读取目标任务时不得把标签清理误报为成功。"""
|
||||
fake_client = MagicMock()
|
||||
fake_client.torrents_info.return_value = []
|
||||
downloader = Qbittorrent.__new__(Qbittorrent)
|
||||
downloader.qbc = fake_client
|
||||
|
||||
with patch.object(qbittorrent_module.time, "sleep") as sleep:
|
||||
cleaned = downloader.delete_torrents_tag("abc123", "tmp-tag-01")
|
||||
|
||||
assert not cleaned
|
||||
assert fake_client.torrents_remove_tags.call_count == 10
|
||||
assert fake_client.torrents_info.call_count == 10
|
||||
assert sleep.call_count == 9
|
||||
fake_client.torrents_delete_tags.assert_called_once_with(tags="tmp-tag-01")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user