fix(search): download torrent files via backend for remote instances#686
fix(search): download torrent files via backend for remote instances#686
Conversation
WalkthroughAdds optional Jackett/indexer handling for URL-based torrent additions: frontend can supply an Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant UI as AddTorrentDialog
participant API as Web API Client
participant Handler as TorrentsHandler
participant Jackett as Jackett Service
participant QB as qBittorrent
User->>UI: Submit URLs (+ optional indexerId)
UI->>API: addTorrent(instanceId, { urls, indexerId? })
API->>Handler: POST /api/torrents (form: urls, indexer_id?)
alt indexer_id provided and Jackett available
loop each URL
Handler->>Handler: normalize & check cancellation
alt URL is magnet
Handler->>QB: Add torrent via URL/magnet
QB-->>Handler: success/failure
else non-magnet URL
Handler->>Jackett: Download torrent (indexer_id, URL)
Jackett-->>Handler: torrent bytes / error
opt download success
Handler->>QB: Add torrent (file content)
QB-->>Handler: success/failure
end
end
end
else no indexer or Jackett unavailable
Handler->>QB: AddTorrentFromURLs (batch URLs)
QB-->>Handler: success/failure per call
end
Handler-->>API: { added, failed, failedURLs?, failedFiles? }
API-->>UI: Response
UI-->>User: Result / toasts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
web/src/lib/api.ts (1)
547-592: addTorrent indexerId plumbing is consistent; consider more explicit null checkThe
indexerIdfield is correctly added to the client payload and mapped toindexer_idinFormData, matching the server’s expectation.To be slightly more robust against unexpected
0values, you could guard on nullish rather than truthiness:- if (data.indexerId) formData.append("indexer_id", data.indexerId.toString()) + if (data.indexerId != null) formData.append("indexer_id", data.indexerId.toString())This keeps behavior identical for positive IDs while avoiding accidental omission if
0ever appears.web/src/components/torrents/AddTorrentDialog.tsx (1)
101-104: Indexer‑aware URL flow in AddTorrentDialog is wired correctlyThe new
indexerIdfield is threaded cleanly:
AddTorrentDropPayloadURL variant carriesindexerId.- The form tracks
indexerIdwith a default ofundefined.- URL‑based submissions forward
indexerIdintoapi.addTorrent, while file‑based submissions ignore it.This matches the backend’s
indexer_idhandling and confines the behavior to the URL tab, so existing file workflows remain unchanged.Also applies to: 135-136, 513-537, 565-586, 674-706
internal/api/handlers/torrents.go (1)
283-305: Indexer‑based URL torrent addition is well‑integrated with existing behaviorThe new
indexer_idhandling inAddTorrentis structured sensibly:
indexer_idis only parsed for URL‑only submissions, matching the frontend’s usage.- When
indexerID > 0andjackettServiceis available:
- Magnets are still added via
AddTorrentFromURLs, preserving the previous path.- Non‑magnet URLs are resolved via
jackettService.DownloadTorrentand then added viaAddTorrent, which fixes the remote‑instance “can’t reach indexer” problem.- Per‑URL success/failure is tracked, and partial failures surface in the response message.
- Without a valid indexer or service, the code falls back to the original
AddTorrentFromURLscall, so existing clients continue to work as before.If you ever need finer‑grained reporting, you could optionally expose the per‑URL failures instead of just counts, but the current behavior is coherent and backwards compatible.
Also applies to: 439-507
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
internal/api/handlers/torrents.go(5 hunks)internal/api/server.go(1 hunks)web/src/components/torrents/AddTorrentDialog.tsx(5 hunks)web/src/lib/api.ts(2 hunks)web/src/pages/Search.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
📚 Learning: 2025-12-03T18:11:08.659Z
Learnt from: finevan
Repo: autobrr/qui PR: 677
File: web/src/components/torrents/AddTorrentDialog.tsx:496-504
Timestamp: 2025-12-03T18:11:08.659Z
Learning: In the AddTorrentDialog component (web/src/components/torrents/AddTorrentDialog.tsx), temporary path settings (useDownloadPath/downloadPath) should be applied per-torrent rather than updating global instance preferences. The UI/UX is designed to suggest that these options apply to the individual torrent being added.
Applied to files:
web/src/pages/Search.tsxweb/src/components/torrents/AddTorrentDialog.tsx
📚 Learning: 2025-11-25T22:46:03.762Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 632
File: internal/backups/service.go:1401-1404
Timestamp: 2025-11-25T22:46:03.762Z
Learning: In qui's backup service (internal/backups/service.go), background torrent downloads initiated during manifest import intentionally use a fire-and-forget pattern with the shared service context (s.ctx). Per-run cancellation is not needed, as orphaned downloads completing after run deletion are considered harmless and acceptable. This design prioritizes simplicity over per-run lifecycle management for background downloads.
Applied to files:
internal/api/server.gointernal/api/handlers/torrents.go
📚 Learning: 2025-11-06T12:11:04.963Z
Learnt from: Audionut
Repo: autobrr/qui PR: 553
File: internal/services/crossseed/service.go:1045-1082
Timestamp: 2025-11-06T12:11:04.963Z
Learning: The autobrr/qui project uses a custom go-qbittorrent client library (github.com/autobrr/go-qbittorrent) that supports both "paused" and "stopped" parameters when adding torrents via the options map. Both parameters should be set together when controlling torrent start state, as seen in internal/services/crossseed/service.go and throughout the codebase.
Applied to files:
internal/api/server.gointernal/api/handlers/torrents.go
📚 Learning: 2025-11-27T15:33:39.007Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
Applied to files:
internal/api/handlers/torrents.go
🧬 Code graph analysis (2)
internal/api/server.go (1)
internal/api/handlers/torrents.go (1)
NewTorrentsHandler(57-62)
internal/api/handlers/torrents.go (2)
internal/services/jackett/service.go (2)
Service(64-89)TorrentDownloadRequest(249-255)internal/api/handlers/helpers.go (1)
RespondError(34-38)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (3)
web/src/pages/Search.tsx (1)
730-751: Indexer context is correctly propagated into AddTorrentDialogPassing
indexerId: result.indexerIdin the drop payload cleanly wires the originating indexer through to the add‑torrent flow for remote instances. The change is consistent with the updatedAddTorrentDropPayloadand backend expectations; no further adjustments needed here.internal/api/server.go (1)
245-255: TorrentsHandler wiring with jackettService looks correctInjecting
s.jackettServiceintoNewTorrentsHandleraligns with the updated constructor and keeps behavior unchanged when the service is nil.internal/api/handlers/torrents.go (1)
25-33: jackettService injection into TorrentsHandler is straightforward and safeAdding
jackettService *jackett.ServicetoTorrentsHandlerand extendingNewTorrentsHandlerto accept it is minimal and keeps callers free to pass nil. Downstream code checks forh.jackettService != nilbefore use, so there’s no risk of nil dereference.Also applies to: 57-62
Tests the new indexer_id functionality that downloads torrent files via the backend for remote qBittorrent instances: - Download via backend when indexer_id provided - Fallback to direct URL method without indexer_id or jackett service - Magnet links bypass jackett and go directly to qBittorrent - Mixed URL/magnet handling - Error handling for download and add failures - Partial success scenarios - Form field parsing
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
internal/api/handlers/torrents_add_test.go (3)
101-108: Consider usingstrings.EqualFoldfor case-insensitive magnet prefix detection.The current check only handles exactly
magnet:orMAGNET:but will miss mixed-case variants likeMagnet:orMaGnEt:. While rare, a more robust check would be:+import "strings" + // Magnet links can be added directly to qBittorrent -if len(url) > 7 && (url[:7] == "magnet:" || url[:7] == "MAGNET:") { +if len(url) > 7 && strings.EqualFold(url[:7], "magnet:") {
457-511: Consider eliminating code duplication withaddTorrentWithIndexer.
addTorrentWithIndexerCustomduplicates ~50 lines fromaddTorrentWithIndexer. SincecustomMockJackettServicehas the sameDownloadTorrentmethod signature, it could implementjackettServiceAdapter:+// Ensure customMockJackettService implements jackettServiceAdapter +var _ jackettServiceAdapter = (*customMockJackettService)(nil) + // addTorrentWithIndexerCustom is like addTorrentWithIndexer but accepts customMockJackettService func addTorrentWithIndexerCustom( ctx context.Context, syncManager syncManagerAdapter, jackettService *customMockJackettService, instanceID int, urls []string, indexerID int, options map[string]string, ) (addedCount int, failedCount int, lastError error) { - if indexerID > 0 && jackettService != nil { - // ... duplicated logic ... - } - return addedCount, failedCount, lastError + return addTorrentWithIndexer(ctx, syncManager, jackettService, instanceID, urls, indexerID, options) }
725-756: Consider using standard library string functions.The custom implementations can be simplified using the standard library:
+import "strings" + // processURLSeparators converts newlines to commas (like the handler) func processURLSeparators(s string) string { - result := make([]byte, 0, len(s)) - for i := 0; i < len(s); i++ { - if s[i] == '\n' { - result = append(result, ',') - } else { - result = append(result, s[i]) - } - } - return string(result) + return strings.ReplaceAll(s, "\n", ",") } // splitURLs splits on comma (like the handler) func splitURLs(s string) []string { - var result []string - var current []byte - for i := 0; i < len(s); i++ { - if s[i] == ',' { - if len(current) > 0 { - result = append(result, string(current)) - current = current[:0] - } - } else { - current = append(current, s[i]) - } - } - if len(current) > 0 { - result = append(result, string(current)) - } - return result + var result []string + for _, part := range strings.Split(s, ",") { + if part != "" { + result = append(result, part) + } + } + return result }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/api/handlers/torrents_add_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
📚 Learning: 2025-11-27T15:33:39.007Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
Applied to files:
internal/api/handlers/torrents_add_test.go
🧬 Code graph analysis (1)
internal/api/handlers/torrents_add_test.go (1)
internal/services/jackett/service.go (1)
TorrentDownloadRequest(249-255)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (5)
internal/api/handlers/torrents_add_test.go (5)
1-20: LGTM!The imports are appropriate for the test file, including the necessary testing utilities from testify and chi for route context handling.
22-70: LGTM!The mock implementations follow standard Go testing patterns with proper call tracking and configurable return values.
72-81: LGTM!The adapter interfaces correctly define the minimal contract needed for testing. The interfaces allow for clean dependency injection in the test helper functions.
140-171: LGTM!Well-structured test with clear assertions verifying the download-via-backend flow. Good separation of require (for prerequisites) and assert (for verifications).
758-775: LGTM!The benchmark correctly resets state between iterations and measures the core logic path.
- Remove unused callCount and originalDownload variables - Fix assertion to only check actual mock calls - Replace buggy custom parseInt with strconv.Atoi 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (5)
internal/api/handlers/torrents_add_test.go (5)
84-139: Core indexer logic duplicated in test-only helpers; consider sharing implementation with the handler
addTorrentWithIndexerandaddTorrentWithIndexerCustomencode the core indexer-aware add logic entirely inside a_test.gofile. If the real handler’s implementation diverges, these tests can still pass while production behavior breaks. On top of that,addTorrentWithIndexerCustomis effectively a copy ofaddTorrentWithIndexer;customMockJackettServicealready satisfiesjackettServiceAdapter, so you can reuse a single helper.Consider:
- Moving this logic into a non-test helper in the handler package and calling it from both the HTTP handler and tests, or
- Driving the tests against the real handler (with mocks) instead of a reimplemented copy, and
- Dropping
addTorrentWithIndexerCustomand usingaddTorrentWithIndexerwithcustomMockJackettServicevia the existingjackettServiceAdapterinterface.This reduces drift risk and avoids maintaining two near-identical code paths.
Also applies to: 451-505
507-592: Clarify whether negativeindexer_idvalues are actually valid
TestParseIndexerIDFromFormasserts that a negativeindexer_idstring parses to a negativeintand preserves that value. Downstream, your logic branches onindexerID > 0, so negative values effectively behave like “no indexer” but the test now enshrines accepting negative IDs.If negative IDs are not meaningful in your domain, it might be safer to:
- Treat non-positive values as “no indexer” at parse time (e.g., clamp
< 1to0), and/or- Adjust the test to expect
0for negative input.Otherwise, keeping this test is fine but it would be good to explicitly document that negative indexer IDs are tolerated but ignored later.
594-625: “Handler” test only exercises form parsing, not the actual handler
TestAddTorrentHandler_IndexerIDInFormcurrently constructs a multipart request, callsParseMultipartForm, and asserts onFormValueresults, but never invokes the realTorrentsHandlermethod. The name suggests a higher-level handler test.Two options to reduce confusion and increase value:
- Either rename the test to reflect that it only validates multipart parsing behavior, or
- Extend it to actually instantiate a
TorrentsHandler, call the appropriate handler function viahttptest.NewRecorder, and assert thatindexer_idand related fields are wired into the sync/jackett calls as expected.That would give you genuine end-to-end coverage of the new indexer-aware path.
627-727: Consider expanding URL-processing coverage (whitespace/CRLF) and reusing handler helpers if available
TestAddTorrentURLProcessingwithprocessURLSeparatorsandsplitURLscurrently handles\nand,separators and ignores empty segments, but does not cover:
- Windows-style
\r\nnewlines,- Leading/trailing spaces around URLs, or
- Multiple consecutive separators.
If real-world input from the UI or API can include those cases, you may want to:
- Either extend these helpers to mirror the production handler’s URL normalization exactly (e.g., trimming spaces, handling
\r\n), or- Reuse the actual handler helpers here (if such functions already exist in non-test code) so behavior and tests stay in lockstep.
Not strictly required for correctness given current expectations but could harden the path against messy input.
729-746: ResetaddTorrentFromURLsCallsin the benchmark loop for future-proofingIn
BenchmarkAddTorrentWithIndexer, you resetmockSync.addTorrentCallsandmockJackett.downloadTorrentCallsinside the loop, but leavemockSync.addTorrentFromURLsCallsuntouched. Today the benchmark path doesn’t hitAddTorrentFromURLs, so this is harmless, but if the logic ever starts using magnets or falls back in some cases, the benchmark will accumulate stale state.You can future-proof this by also resetting
addTorrentFromURLsCallsinside the loop:for i := 0; i < b.N; i++ { - mockSync.addTorrentCalls = nil + mockSync.addTorrentCalls = nil + mockSync.addTorrentFromURLsCalls = nil mockJackett.downloadTorrentCalls = nil _, _, _ = addTorrentWithIndexer(ctx, mockSync, mockJackett, 1, urls, 42, options) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/api/handlers/torrents_add_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
📚 Learning: 2025-11-27T15:33:39.007Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
Applied to files:
internal/api/handlers/torrents_add_test.go
📚 Learning: 2025-11-25T22:46:03.762Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 632
File: internal/backups/service.go:1401-1404
Timestamp: 2025-11-25T22:46:03.762Z
Learning: In qui's backup service (internal/backups/service.go), background torrent downloads initiated during manifest import intentionally use a fire-and-forget pattern with the shared service context (s.ctx). Per-run cancellation is not needed, as orphaned downloads completing after run deletion are considered harmless and acceptable. This design prioritizes simplicity over per-run lifecycle management for background downloads.
Applied to files:
internal/api/handlers/torrents_add_test.go
🧬 Code graph analysis (1)
internal/api/handlers/torrents_add_test.go (1)
internal/services/jackett/service.go (1)
TorrentDownloadRequest(249-255)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (1)
internal/api/handlers/torrents_add_test.go (1)
141-252: Indexer-aware and magnet-path tests look comprehensiveThe tests around
addTorrentWithIndexerfor pure indexer downloads, fallback, magnet-only, and mixed magnet/HTTP flows exercise the important branches (Jackett vs direct URL, magnet detection, and proper call routing). The use of mocks and explicit call assertions makes the intended behavior clear.No changes needed here.
- Remove duplicate addTorrentWithIndexerCustom, reuse existing helper via jackettServiceAdapter interface - Rename misleading test to TestMultipartFormParsing_IndexerID - Document negative indexer ID handling behavior - Reset all mock slices in benchmark for future-proofing
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
internal/api/handlers/torrents_add_test.go (3)
84-139: Avoid duplicating core torrent/indexer logic in test-onlyaddTorrentWithIndexerand consider simplifying magnet detection.This helper fully reimplements the handler’s indexer-aware add logic but lives only in tests, so changes in the real handler won’t automatically be exercised. Consider factoring this into a shared unexported helper in the production code and calling it from both the handler and tests, or driving tests through the real handler with mocks, to avoid drift.
Also, the magnet detection currently relies on manual slicing and only accepts
magnet:andMAGNET:; usingstrings.HasPrefix/strings.EqualFold(with alen(url) >= len("magnet:")guard) would be clearer and more robust to casing edge cases.
643-674: Consider reusing the production URL-normalization helpers or standard library functions.
processURLSeparatorsandsplitURLsmanually implement newline-to-comma replacement and comma-splitting. If the handler already has equivalent helpers, it would be safer to call those from tests (or move them into a shared internal package) instead of reimplementing them here. If not, and exact matching with handler code isn’t required, you could simplify this usingstrings.ReplaceAllplusstrings.Splitand optional trimming.
676-694: Verifyb.Loop()usage against your Go version and CI toolchain.
b.Loop()is only available in newer Go releases; if your module or CI still targets Go versions without this API, the benchmark won’t compile. For broader compatibility you can fall back to the classic pattern:func BenchmarkAddTorrentWithIndexer(b *testing.B) { @@ - b.ResetTimer() - for b.Loop() { + b.ResetTimer() + for i := 0; i < b.N; i++ { mockSync.addTorrentCalls = nil mockSync.addTorrentFromURLsCalls = nil mockJackett.downloadTorrentCalls = nil _, _, _ = addTorrentWithIndexer(ctx, mockSync, mockJackett, 1, urls, 42, options) } }Please confirm that your Go toolchain supports
testing.B.Loopif you keep the current form.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/api/handlers/torrents_add_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
📚 Learning: 2025-11-27T15:33:39.007Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
Applied to files:
internal/api/handlers/torrents_add_test.go
📚 Learning: 2025-11-25T22:46:03.762Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 632
File: internal/backups/service.go:1401-1404
Timestamp: 2025-11-25T22:46:03.762Z
Learning: In qui's backup service (internal/backups/service.go), background torrent downloads initiated during manifest import intentionally use a fire-and-forget pattern with the shared service context (s.ctx). Per-run cancellation is not needed, as orphaned downloads completing after run deletion are considered harmless and acceptable. This design prioritizes simplicity over per-run lifecycle management for background downloads.
Applied to files:
internal/api/handlers/torrents_add_test.go
🧬 Code graph analysis (1)
internal/api/handlers/torrents_add_test.go (1)
internal/services/jackett/service.go (1)
TorrentDownloadRequest(249-255)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (3)
internal/api/handlers/torrents_add_test.go (3)
141-371: Index-aware add tests give solid coverage of success, failure, and mixed URL scenarios.The suite around
addTorrentWithIndexer(direct downloads, magnets-only, mixed URLs, download failures, and add failures usingcustomMockJackettService) exercises the intended added/failed counts andlastErrorsemantics well, and the per-call recording on the mocks makes the behavior easy to reason about.
451-572: Form parsing tests accurately pin downindexer_id,urls, andcategorybehavior.The multipart-based tests validate
indexer_idparsing across valid/invalid/empty/negative cases and confirm the raw form values forurlsandcategory, which should catch regressions in how the handler reads those fields.
574-641: URL normalization tests cover the key separator transformations.The table-driven cases for single, newline-separated, comma-separated, and mixed magnet/HTTP URLs give good confidence that the handler’s URL normalization (as emulated by these helpers) behaves as expected.
…ions - Return 400 Bad Request for invalid/negative/zero indexer_id instead of silently falling back to direct URL method - Return 503 Service Unavailable when indexer_id provided but jackett service is not configured - Add instanceID to download error logs for better debugging - Add url and indexerID to add-torrent error logs - Track failed URLs and include them in API response for partial failures - Log skipped empty URLs at debug level - Align test helper magnet detection with handler implementation - Add HTTP handler integration tests for new error paths
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
internal/api/handlers/torrents_add_test.go (2)
85-141: Good extraction of core logic for unit testing.The
addTorrentWithIndexerfunction mirrors the handler's indexer-aware logic, allowing focused unit tests without HTTP concerns. Be aware that this creates a synchronization burden—changes to the handler logic must be reflected here.Consider adding a code comment noting that this function must stay in sync with
TorrentsHandler.AddTorrent, or alternatively, refactoring the handler to call this shared function to avoid divergence.
645-676: Custom URL processing helpers duplicatestrings.ReplaceAllandstrings.Splitbehavior.These helper functions (
processURLSeparators,splitURLs) reimplement standard library functionality. The handler usesstrings.ReplaceAllandstrings.Splitdirectly.Consider simplifying these helpers:
-// processURLSeparators converts newlines to commas (like the handler) -func processURLSeparators(s string) string { - result := make([]byte, 0, len(s)) - for i := 0; i < len(s); i++ { - if s[i] == '\n' { - result = append(result, ',') - } else { - result = append(result, s[i]) - } - } - return string(result) -} - -// splitURLs splits on comma (like the handler) -func splitURLs(s string) []string { - var result []string - var current []byte - for i := 0; i < len(s); i++ { - if s[i] == ',' { - if len(current) > 0 { - result = append(result, string(current)) - current = current[:0] - } - } else { - current = append(current, s[i]) - } - } - if len(current) > 0 { - result = append(result, string(current)) - } - return result -} +// processURLSeparators converts newlines to commas (like the handler) +func processURLSeparators(s string) string { + return strings.ReplaceAll(s, "\n", ",") +} + +// splitURLs splits on comma (like the handler) +func splitURLs(s string) []string { + return strings.Split(s, ",") +}Note: The handler's
strings.Splitdoesn't filter empty strings, but your custom implementation does. If that's intentional, document why the test needs different behavior.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/api/handlers/torrents.go(7 hunks)internal/api/handlers/torrents_add_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
📚 Learning: 2025-11-27T15:33:39.007Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
Applied to files:
internal/api/handlers/torrents.gointernal/api/handlers/torrents_add_test.go
📚 Learning: 2025-11-25T22:46:03.762Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 632
File: internal/backups/service.go:1401-1404
Timestamp: 2025-11-25T22:46:03.762Z
Learning: In qui's backup service (internal/backups/service.go), background torrent downloads initiated during manifest import intentionally use a fire-and-forget pattern with the shared service context (s.ctx). Per-run cancellation is not needed, as orphaned downloads completing after run deletion are considered harmless and acceptable. This design prioritizes simplicity over per-run lifecycle management for background downloads.
Applied to files:
internal/api/handlers/torrents.gointernal/api/handlers/torrents_add_test.go
📚 Learning: 2025-11-06T12:11:04.963Z
Learnt from: Audionut
Repo: autobrr/qui PR: 553
File: internal/services/crossseed/service.go:1045-1082
Timestamp: 2025-11-06T12:11:04.963Z
Learning: The autobrr/qui project uses a custom go-qbittorrent client library (github.com/autobrr/go-qbittorrent) that supports both "paused" and "stopped" parameters when adding torrents via the options map. Both parameters should be set together when controlling torrent start state, as seen in internal/services/crossseed/service.go and throughout the codebase.
Applied to files:
internal/api/handlers/torrents.go
🧬 Code graph analysis (2)
internal/api/handlers/torrents.go (3)
internal/services/crossseed/service.go (1)
Service(236-287)internal/services/jackett/service.go (2)
Service(64-89)TorrentDownloadRequest(249-255)internal/api/handlers/helpers.go (2)
RespondError(34-38)RespondJSON(22-31)
internal/api/handlers/torrents_add_test.go (2)
internal/services/jackett/service.go (1)
TorrentDownloadRequest(249-255)internal/api/handlers/torrents.go (1)
NewTorrentsHandler(57-62)
🔇 Additional comments (12)
internal/api/handlers/torrents.go (7)
30-32: LGTM! Handler struct and constructor updated correctly for Jackett integration.The
TorrentsHandlerstruct now properly includesjackettServiceas an optional dependency, and the constructor wires it correctly.Also applies to: 57-61
284-310: Solid validation forindexer_idwith clear error messages.The validation correctly:
- Parses
indexer_idonly when URLs are provided (not for file uploads)- Returns 400 with a descriptive message for non-integer values
- Returns 400 for non-positive integers
418-426: Good addition of structured error tracking for per-URL failures.The
failedURLstruct provides useful feedback to clients about which specific URLs failed and why.
453-460: Appropriate 503 response when Jackett service is unavailable.When a client provides
indexer_idbut the Jackett service isn't configured, returning 503 Service Unavailable with a helpful message is the right approach.
461-520: Well-structured per-URL processing with proper error handling.The logic correctly:
- Skips empty URLs with debug logging
- Handles context cancellation for graceful shutdown
- Passes magnet links directly to qBittorrent (case-insensitive check)
- Downloads non-magnet URLs via Jackett, then adds via
AddTorrent- Continues processing remaining URLs on individual failures
- Tracks per-URL failures with specific error messages
521-533: Fallback path correctly preserved for non-indexer flows.When
indexer_idis not provided, the handler falls back to the originalAddTorrentFromURLsbehavior, maintaining backward compatibility.
556-564: Response properly includes failed URL details when applicable.The conditional addition of
failedURLsto the response provides actionable feedback without cluttering successful responses.internal/api/handlers/torrents_add_test.go (5)
24-72: Well-structured mock implementations for testing.The mock types cleanly capture call history and allow configurable error responses, enabling thorough testing of both success and failure paths.
289-323: Dead code from past review has been addressed.The previous review flagged unused
callCountandoriginalDownloadvariables. These have been removed, and line 319 now correctly uses onlylen(customJackett.calls).
706-733: Good integration test for invalid indexer_id handling.This test properly verifies that non-integer
indexer_idvalues return 400 with an appropriate error message.
789-817: Test correctly verifies 503 response when Jackett is unavailable.This ensures the handler returns the appropriate service unavailable error when
indexer_idis provided butjackettServiceis nil.
143-429: Comprehensive test coverage for indexer-aware torrent addition.The tests cover key scenarios:
- Download via backend with valid indexer
- Fallback without indexer ID
- Fallback with nil Jackett service
- Magnet link passthrough (including case-insensitivity)
- Mixed URLs and magnets
- Partial failures with continuation
- Empty URL handling
- Add interface-based testing support with torrentAdder and torrentDownloader interfaces for mocking dependencies - Add NewTorrentsHandlerForTesting constructor for test isolation - Add handler integration tests covering: - Successful indexer download flow (HTTP 201) - Magnet links bypassing indexer download - Mixed URLs and magnets handling - Partial failure with failedURLs response - Direct URL fallback when no indexer_id - Track and report failed file uploads in response with failedFiles - Change file read errors from Error to Warn log level
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
internal/api/handlers/torrents.go(9 hunks)web/src/components/torrents/AddTorrentDialog.tsx(5 hunks)web/src/lib/api.ts(4 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
📚 Learning: 2025-12-03T18:11:08.659Z
Learnt from: finevan
Repo: autobrr/qui PR: 677
File: web/src/components/torrents/AddTorrentDialog.tsx:496-504
Timestamp: 2025-12-03T18:11:08.659Z
Learning: In the AddTorrentDialog component (web/src/components/torrents/AddTorrentDialog.tsx), temporary path settings (useDownloadPath/downloadPath) should be applied per-torrent rather than updating global instance preferences. The UI/UX is designed to suggest that these options apply to the individual torrent being added.
Applied to files:
web/src/components/torrents/AddTorrentDialog.tsx
📚 Learning: 2025-11-25T22:46:03.762Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 632
File: internal/backups/service.go:1401-1404
Timestamp: 2025-11-25T22:46:03.762Z
Learning: In qui's backup service (internal/backups/service.go), background torrent downloads initiated during manifest import intentionally use a fire-and-forget pattern with the shared service context (s.ctx). Per-run cancellation is not needed, as orphaned downloads completing after run deletion are considered harmless and acceptable. This design prioritizes simplicity over per-run lifecycle management for background downloads.
Applied to files:
web/src/lib/api.tsinternal/api/handlers/torrents.go
📚 Learning: 2025-11-27T15:33:39.007Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
Applied to files:
internal/api/handlers/torrents.go
📚 Learning: 2025-11-06T12:11:04.963Z
Learnt from: Audionut
Repo: autobrr/qui PR: 553
File: internal/services/crossseed/service.go:1045-1082
Timestamp: 2025-11-06T12:11:04.963Z
Learning: The autobrr/qui project uses a custom go-qbittorrent client library (github.com/autobrr/go-qbittorrent) that supports both "paused" and "stopped" parameters when adding torrents via the options map. Both parameters should be set together when controlling torrent start state, as seen in internal/services/crossseed/service.go and throughout the codebase.
Applied to files:
internal/api/handlers/torrents.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Build Docker images (linux/arm/v7)
- GitHub Check: Build Docker images (linux/arm64)
- GitHub Check: Build Docker images (linux/amd64/v2)
- GitHub Check: Build Docker images (linux/amd64)
- GitHub Check: Build Docker images (linux/arm/v6)
- GitHub Check: Build Docker images (linux/amd64/v3)
- GitHub Check: Build distribution binaries
🔇 Additional comments (4)
web/src/lib/api.ts (1)
568-568: LGTM! Clean addition of indexerId parameter.The optional
indexerIdfield is properly typed and correctly appended to the form data with appropriate snake_case naming for the backend.Also applies to: 594-594
web/src/components/torrents/AddTorrentDialog.tsx (1)
104-104: LGTM! Proper indexerId threading through form state.The
indexerIdfield is correctly:
- Typed as optional in both the drop payload and form data interfaces
- Limited to URL-based additions (not file uploads)
- Initialized with a proper default value
- Propagated from drop payloads to form state
- Conditionally included in API submissions
Also applies to: 136-136, 528-530, 579-579, 690-690
internal/api/handlers/torrents.go (2)
72-122: LGTM! Clean testing architecture.The testing interfaces and wrapper methods provide a clean way to inject mocks without coupling production code to test infrastructure. The consistent delegation pattern (mock-first, then real implementation) is well-executed.
536-616: Well-structured indexer-based torrent download flow.The implementation correctly:
- Validates jackett service availability before attempting downloads
- Processes each URL individually with cancellation checks
- Handles magnet links separately (they bypass indexer downloads)
- Tracks per-URL failures for clear user feedback
- Maintains backward compatibility when no indexerID is provided
The per-URL error tracking is a significant UX improvement over the previous all-or-nothing approach.
- Add AddTorrentResponse type matching backend response shape - Update api.addTorrent return type to include added/failed counts - Show toast notifications with success, error, or partial failure info - Display up to 3 failure details for failed URLs/files
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
web/src/components/torrents/AddTorrentDialog.tsx (1)
536-578: Enhanced response handling with detailed user feedback.The toast logic correctly handles all success, all failure, and partial success scenarios. Failure details are appropriately limited to the first 3 items for readability.
Optional: Consider truncating long URLs in toast descriptions.
Long download URLs in failure messages may be difficult to read in toasts. Consider truncating URLs to ~50 characters with ellipsis for better UX:
const failedDetails = [ - ...(response.failedURLs?.map(f => `${f.url}: ${f.error}`) ?? []), + ...(response.failedURLs?.map(f => { + const truncatedUrl = f.url.length > 50 ? f.url.slice(0, 47) + '...' : f.url + return `${truncatedUrl}: ${f.error}` + }) ?? []), ...(response.failedFiles?.map(f => `${f.filename}: ${f.error}`) ?? []) ]Apply similar truncation at line 570-572.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
web/src/components/torrents/AddTorrentDialog.tsx(7 hunks)web/src/lib/api.ts(5 hunks)web/src/types/index.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- web/src/lib/api.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.
📚 Learning: 2025-12-03T18:11:08.659Z
Learnt from: finevan
Repo: autobrr/qui PR: 677
File: web/src/components/torrents/AddTorrentDialog.tsx:496-504
Timestamp: 2025-12-03T18:11:08.659Z
Learning: In the AddTorrentDialog component (web/src/components/torrents/AddTorrentDialog.tsx), temporary path settings (useDownloadPath/downloadPath) should be applied per-torrent rather than updating global instance preferences. The UI/UX is designed to suggest that these options apply to the individual torrent being added.
Applied to files:
web/src/types/index.tsweb/src/components/torrents/AddTorrentDialog.tsx
📚 Learning: 2025-11-06T12:11:04.963Z
Learnt from: Audionut
Repo: autobrr/qui PR: 553
File: internal/services/crossseed/service.go:1045-1082
Timestamp: 2025-11-06T12:11:04.963Z
Learning: The autobrr/qui project uses a custom go-qbittorrent client library (github.com/autobrr/go-qbittorrent) that supports both "paused" and "stopped" parameters when adding torrents via the options map. Both parameters should be set together when controlling torrent start state, as seen in internal/services/crossseed/service.go and throughout the codebase.
Applied to files:
web/src/components/torrents/AddTorrentDialog.tsx
🧬 Code graph analysis (1)
web/src/components/torrents/AddTorrentDialog.tsx (1)
web/src/types/index.ts (1)
AddTorrentResponse(352-358)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (7)
web/src/types/index.ts (1)
342-358: LGTM! Well-structured failure reporting interfaces.The new interfaces provide clear per-URL and per-file failure reporting, enabling detailed user feedback. The optional failure arrays in
AddTorrentResponseappropriately handle success scenarios where no failures occur.web/src/components/torrents/AddTorrentDialog.tsx (6)
42-42: LGTM! Correct type import.The
AddTorrentResponseimport enables proper typing of the API response in the mutation'sonSuccesshandler.
103-105: LGTM! Backward-compatible payload extension.The optional
indexerIdfield enables indexer-aware URL handling while maintaining compatibility with existing code paths.
137-137: LGTM!Adding
indexerIdtoFormDataappropriately extends the form state to track indexer context for URL-based additions.
529-531: LGTM! Correct conditional propagation.The
indexerIdis appropriately included only for URL-based submissions when present, aligning with backend indexer-aware handling.
608-608: LGTM!Initializing
indexerIdtoundefinedis correct for an optional field in the form defaults.
719-719: LGTM! Completes the indexerId data flow.Propagating
indexerIdfrom the drop payload into form state ensures the indexer context is preserved through URL-based torrent additions from the Search page.
This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.8.1` -> `v1.9.1` | --- ### Release Notes <details> <summary>autobrr/qui (ghcr.io/autobrr/qui)</summary> ### [`v1.9.1`](https://github.com/autobrr/qui/releases/tag/v1.9.1) [Compare Source](autobrr/qui@v1.9.0...v1.9.1) #### Changelog ##### Bug Fixes - [`441418b`](autobrr/qui@441418b): fix(api): remove user\_id session check from dashboard settings ([#​711](autobrr/qui#711)) ([@​s0up4200](https://github.com/s0up4200)) - [`bd2587b`](autobrr/qui@bd2587b): fix(db): resolve cross-seed settings mutual exclusivity lockout ([#​714](autobrr/qui#714)) ([@​s0up4200](https://github.com/s0up4200)) **Full Changelog**: <autobrr/qui@v1.9.0...v1.9.1> #### Docker images - `docker pull ghcr.io/autobrr/qui:v1.9.1` - `docker pull ghcr.io/autobrr/qui:latest` #### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! ### [`v1.9.0`](https://github.com/autobrr/qui/releases/tag/v1.9.0) [Compare Source](autobrr/qui@v1.8.1...v1.9.0) #### Changelog ##### Important Cross-seeds are now added to `.cross`-suffixed categories by default. This is opt-out. The old delay logic is removed. ##### Highlights - Customize your Dashboard-page (order, visibility) - Tracker Breakdown section in Dashboard with import/export functionality - Warnings and actions for cross-seeds when you attempt to delete torrents - Show free space in torrent table footer ##### New Features - [`1aa7360`](autobrr/qui@1aa7360): feat(dashboard): tracker breakdown and customizable layout ([#​637](autobrr/qui#637)) ([@​s0up4200](https://github.com/s0up4200)) - [`85fd74b`](autobrr/qui@85fd74b): feat(jackett): propagate 429 rate limits with retry and cooldown ([#​684](autobrr/qui#684)) ([@​s0up4200](https://github.com/s0up4200)) - [`a5777c4`](autobrr/qui@a5777c4): feat(reannounce): add configurable max retries setting ([#​685](autobrr/qui#685)) ([@​s0up4200](https://github.com/s0up4200)) - [`6451e56`](autobrr/qui@6451e56): feat(settings): add TMM relocation behavior settings ([#​664](autobrr/qui#664)) ([@​s0up4200](https://github.com/s0up4200)) - [`680fd25`](autobrr/qui@680fd25): feat(torrents): add confirmation dialogs for TMM and Set Location ([#​687](autobrr/qui#687)) ([@​s0up4200](https://github.com/s0up4200)) - [`7f779f9`](autobrr/qui@7f779f9): feat(torrents): warn about cross-seeded torrents in delete dialogs ([#​670](autobrr/qui#670)) ([@​s0up4200](https://github.com/s0up4200)) - [`1c489bc`](autobrr/qui@1c489bc): feat(ui): persist category collapse state in sidebar ([#​692](autobrr/qui#692)) ([@​jabloink](https://github.com/jabloink)) - [`bdf807e`](autobrr/qui@bdf807e): feat(web): Torrent list details bar shows free space ([#​691](autobrr/qui#691)) ([@​finevan](https://github.com/finevan)) ##### Bug Fixes - [`9db8346`](autobrr/qui@9db8346): fix(crossseed): use matched torrent save path instead of category path ([#​700](autobrr/qui#700)) ([@​s0up4200](https://github.com/s0up4200)) - [`40d7778`](autobrr/qui@40d7778): fix(instance): intern empty string on demand for bypass auth ([#​693](autobrr/qui#693)) ([@​s0up4200](https://github.com/s0up4200)) - [`0aaf39e`](autobrr/qui@0aaf39e): fix(jackett): fetch indexer capabilities in parallel with retries ([#​701](autobrr/qui#701)) ([@​s0up4200](https://github.com/s0up4200)) - [`50e585b`](autobrr/qui@50e585b): fix(qbittorrent): cache tracker health counts in background ([#​662](autobrr/qui#662)) ([@​KyleSanderson](https://github.com/KyleSanderson)) - [`298ca05`](autobrr/qui@298ca05): fix(search): download torrent files via backend for remote instances ([#​686](autobrr/qui#686)) ([@​s0up4200](https://github.com/s0up4200)) - [`27ee31a`](autobrr/qui@27ee31a): fix(torrents): AddTorrentDialog uses the downloadPath api ([#​677](autobrr/qui#677)) ([@​finevan](https://github.com/finevan)) - [`2427fdd`](autobrr/qui@2427fdd): fix(ui): use full category paths in multi-select ([#​683](autobrr/qui#683)) ([@​jabloink](https://github.com/jabloink)) - [`917c65e`](autobrr/qui@917c65e): fix(web): add iOS Safari compatibility for torrent file picker ([#​707](autobrr/qui#707)) ([@​s0up4200](https://github.com/s0up4200)) - [`2ccdc28`](autobrr/qui@2ccdc28): fix(web): dont hide free space when disk is full ([#​694](autobrr/qui#694)) ([@​ewenjo](https://github.com/ewenjo)) ##### Other Changes - [`d684442`](autobrr/qui@d684442): chore(deps): bump the golang group with 7 updates ([#​660](autobrr/qui#660)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`e1267fa`](autobrr/qui@e1267fa): chore(deps): bump the npm group across 1 directory with 29 updates ([#​663](autobrr/qui#663)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`8671971`](autobrr/qui@8671971): docs: Update README to remove size field description ([#​695](autobrr/qui#695)) ([@​s0up4200](https://github.com/s0up4200)) **Full Changelog**: <autobrr/qui@v1.8.1...v1.9.0> #### Docker images - `docker pull ghcr.io/autobrr/qui:v1.9.0` - `docker pull ghcr.io/autobrr/qui:latest` #### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zOS4xIiwidXBkYXRlZEluVmVyIjoiNDIuMzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW1hZ2UiXX0=--> Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/2366 Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net> Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
Summary by CodeRabbit
New Features
Behavior Changes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.