From e6aac59954b88e833e9bbd0116b58666b44bf194 Mon Sep 17 00:00:00 2001 From: Estrella Pan Date: Thu, 2 Jul 2026 11:39:10 +0200 Subject: [PATCH] fix(downloader): make torrent deletion actually delete and report failures (#1046) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit torrents_delete received a Python list, which httpx form-encodes as repeated 'hashes' fields that qBittorrent ignores — pipe-join it as the API expects. Check the response status instead of logging success unconditionally, and propagate the failure up through delete_torrent to the API ResponseModel. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_014w1Z6Nxy6XTRgkFXqPr9Zh --- .../src/module/downloader/client/mock_downloader.py | 10 ++++++++-- backend/src/module/downloader/download_client.py | 10 +++++++--- backend/src/module/manager/torrent.py | 8 +++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/backend/src/module/downloader/client/mock_downloader.py b/backend/src/module/downloader/client/mock_downloader.py index e900242d..49428031 100644 --- a/backend/src/module/downloader/client/mock_downloader.py +++ b/backend/src/module/downloader/client/mock_downloader.py @@ -110,13 +110,19 @@ class MockDownloader: ) return True - async def torrents_delete(self, hash: str, delete_files: bool = True): - hashes = hash.split("|") if "|" in hash else [hash] + async def torrents_delete( + self, hash: str | list, delete_files: bool = True + ) -> bool: + if isinstance(hash, (list, tuple)): + hashes = list(hash) + else: + hashes = hash.split("|") if "|" in hash else [hash] for h in hashes: self._torrents.pop(h, None) logger.debug( "[MockDownloader] torrents_delete(%s, delete_files=%s)", hash, delete_files ) + return True async def torrents_pause(self, hashes: str): for h in hashes.split("|"): diff --git a/backend/src/module/downloader/download_client.py b/backend/src/module/downloader/download_client.py index a587eec9..8e06f960 100644 --- a/backend/src/module/downloader/download_client.py +++ b/backend/src/module/downloader/download_client.py @@ -144,9 +144,13 @@ class DownloadClient(TorrentPath): logger.debug("[Downloader] Rename failed: %s >> %s", old_path, new_path) return result - async def delete_torrent(self, hashes, delete_files: bool = True): - await self.client.torrents_delete(hashes, delete_files=delete_files) - logger.info("[Downloader] Remove torrents.") + async def delete_torrent(self, hashes, delete_files: bool = True) -> bool: + ok = await self.client.torrents_delete(hashes, delete_files=delete_files) + if ok: + logger.info("[Downloader] Remove torrents.") + else: + logger.error("[Downloader] Failed to remove torrents.") + return ok async def pause_torrent(self, hashes: str): await self.client.torrents_pause(hashes) diff --git a/backend/src/module/manager/torrent.py b/backend/src/module/manager/torrent.py index 1bd28f94..34f2e971 100644 --- a/backend/src/module/manager/torrent.py +++ b/backend/src/module/manager/torrent.py @@ -25,7 +25,13 @@ class TorrentManager(Database): async def delete_torrents(self, data: Bangumi, client: DownloadClient): hash_list = await self.__match_torrents_list(data) if hash_list: - await client.delete_torrent(hash_list) + if not await client.delete_torrent(hash_list): + return ResponseModel( + status_code=500, + status=False, + msg_en=f"Failed to delete torrents for {data.official_title}", + msg_zh=f"删除 {data.official_title} 种子失败", + ) logger.info(f"Delete rule and torrents for {data.official_title}") return ResponseModel( status_code=200,