fix(review): tighten qB login check, propagate delete failures end-to-end, close cache race

Post-review fixes for the batch-3.2.8 branch:

- auth() and the setup wizard again require a positive success marker on
  200 responses ('Ok.' body) so a proxy or non-qB service answering
  200 + HTML is not mistaken for a login; 204 stays the qB >= 5.2 success.
- delete_rule no longer wraps a failed torrent deletion into a 200 reply,
  and the WebUI torrents-delete endpoint reports the failure instead of
  answering 'Torrents deleted' unconditionally.
- The close-on-failed-auth teardown now lives once in
  DownloadClient.__aenter__ (client.logout() before raising) instead of
  being copy-pasted into each concrete client.
- search_all() records a cache generation before querying and skips the
  cache write if an invalidation landed in between, so a stale snapshot
  can no longer overwrite a newer invalidation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014w1Z6Nxy6XTRgkFXqPr9Zh
This commit is contained in:
Estrella Pan
2026-07-02 11:57:35 +02:00
parent fa24ec4e2a
commit fe7298689a
12 changed files with 107 additions and 31 deletions

View File

@@ -53,8 +53,14 @@ async def resume_torrents(req: TorrentHashesRequest):
async def delete_torrents(req: TorrentDeleteRequest):
hashes = "|".join(req.hashes)
async with DownloadClient() as client:
await client.delete_torrent(hashes, delete_files=req.delete_files)
return {"msg_en": "Torrents deleted", "msg_zh": "种子已删除"}
ok = await client.delete_torrent(hashes, delete_files=req.delete_files)
if not ok:
return {
"status": False,
"msg_en": "Failed to delete torrents",
"msg_zh": "删除种子失败",
}
return {"status": True, "msg_en": "Torrents deleted", "msg_zh": "种子已删除"}
@router.post("/torrents/tag", dependencies=[Depends(get_current_user)])

View File

@@ -162,10 +162,11 @@ async def test_downloader(req: TestDownloaderRequest):
data={"username": req.username, "password": req.password},
)
# qBittorrent < 5.2 answers 200 + "Ok."; >= 5.2 answers 204 with
# an empty body on success (#1044).
if (
login_resp.status_code in (200, 204)
and "fails" not in login_resp.text.lower()
# an empty body on success (#1044). Keep the positive body check
# for 200 so a proxy answering 200 + HTML is not reported as a
# working login.
if login_resp.status_code == 204 or (
login_resp.status_code == 200 and "ok" in login_resp.text.lower()
):
return TestResultResponse(
success=True,