diff --git a/app/chain/download/tasks.py b/app/chain/download/tasks.py index ca93bb041..8058087c0 100644 --- a/app/chain/download/tasks.py +++ b/app/chain/download/tasks.py @@ -100,7 +100,9 @@ class DownloadTaskOwner(_DownloadOwnerBase): def _download_file_deleted(self, event: Event) -> None: """ - 下载文件删除时,同步删除下载任务 + 下载文件删除时,按查询到的下载器实例同步删除任务并通知下游。 + + 旧 provider 未返回实例名时保留默认实例调用,兼容既有插件实现。 """ if not event: return @@ -111,7 +113,18 @@ class DownloadTaskOwner(_DownloadOwnerBase): # 先查询种子 torrents: List[_SchemaDownloaderTorrent] = self.list_torrents(hashs=[hash_str]) or [] if torrents: - self.remove_torrents(hashs=[hash_str], delete_file=False) + downloaders = list(dict.fromkeys( + torrent.downloader for torrent in torrents if torrent.downloader + )) + if downloaders: + for downloader in downloaders: + self.remove_torrents( + hashs=[hash_str], + delete_file=False, + downloader=downloader, + ) + else: + self.remove_torrents(hashs=[hash_str], delete_file=False) # 发出下载任务删除事件,如需处理辅种,可监听该事件 self.eventmanager.send_event(EventType.DownloadDeleted, { "hash": hash_str, diff --git a/tests/test_download_compat.py b/tests/test_download_compat.py index 7973e768d..b002ff0d5 100644 --- a/tests/test_download_compat.py +++ b/tests/test_download_compat.py @@ -4,7 +4,7 @@ import importlib import inspect import pickle from typing import Optional -from unittest.mock import MagicMock +from unittest.mock import MagicMock, call import pytest @@ -103,7 +103,7 @@ def test_download_file_deleted_removes_task_without_deleting_files() -> None: """源文件删除后按 hash 清理任务并发布删除前快照。""" hash_string = "download-hash" torrent = DownloaderTorrent( - downloader="qbittorrent", + downloader="tr", hash=hash_string, title="Demo.Release", ) @@ -120,6 +120,7 @@ def test_download_file_deleted_removes_task_without_deleting_files() -> None: chain.remove_torrents.assert_called_once_with( hashs=[hash_string], delete_file=False, + downloader="tr", ) chain.eventmanager.send_event.assert_called_once_with( EventType.DownloadDeleted, @@ -127,6 +128,32 @@ def test_download_file_deleted_removes_task_without_deleting_files() -> None: ) +def test_download_file_deleted_removes_task_from_each_matching_downloader() -> None: + """同一 hash 命中多个下载器实例时,应逐个指定实例清理。""" + hash_string = "download-hash" + torrents = [ + DownloaderTorrent(downloader="qb", hash=hash_string), + DownloaderTorrent(downloader="tr", hash=hash_string), + ] + chain = DownloadChain.__new__(DownloadChain) + chain.list_torrents = MagicMock(return_value=torrents) + chain.remove_torrents = MagicMock() + chain.eventmanager = MagicMock() + + chain.download_file_deleted( + Event(EventType.DownloadFileDeleted, {"hash": hash_string}) + ) + + assert chain.remove_torrents.call_args_list == [ + call(hashs=[hash_string], delete_file=False, downloader="qb"), + call(hashs=[hash_string], delete_file=False, downloader="tr"), + ] + chain.eventmanager.send_event.assert_called_once_with( + EventType.DownloadDeleted, + {"hash": hash_string, "torrents": [torrent.model_dump() for torrent in torrents]}, + ) + + @pytest.mark.parametrize( ("event", "expected_query"), [