fix(jackett): fetch indexer capabilities in parallel with retries#701
fix(jackett): fetch indexer capabilities in parallel with retries#701
Conversation
WalkthroughDiscovery was made context-aware and reworked to fetch capabilities and categories in parallel with retries and cancellation; Jackett indexer models gained Categories and a DiscoveryResult wrapper; API handlers and frontend were updated to accept/persist categories and surface discovery warnings. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (6)
🧰 Additional context used🧠 Learnings (4)📚 Learning: 2025-11-27T15:33:39.007ZApplied to files:
📚 Learning: 2025-11-06T11:59:21.390ZApplied to files:
📚 Learning: 2025-12-03T18:11:08.682ZApplied to files:
📚 Learning: 2025-11-25T22:46:03.762ZApplied to files:
🧬 Code graph analysis (4)internal/api/handlers/jackett.go (2)
web/src/lib/api.ts (2)
web/src/types/index.ts (2)
internal/services/jackett/client.go (3)
⏰ 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)
🔇 Additional comments (16)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
internal/services/jackett/client.go (1)
674-699: Consider accepting a parent context for cancellation propagation.The function uses
context.Background()internally, so retries will continue even if the caller's context is cancelled. For discovery operations this is acceptable, but if this function is reused elsewhere, consider accepting a parent context to enable early termination:-func fetchCapabilitiesWithRetry(baseURL, apiKey string, backend models.TorznabBackend, indexerID string, maxRetries int, retryDelay, timeout time.Duration) ([]string, error) { +func fetchCapabilitiesWithRetry(ctx context.Context, baseURL, apiKey string, backend models.TorznabBackend, indexerID string, maxRetries int, retryDelay, timeout time.Duration) ([]string, error) { client := NewClient(baseURL, apiKey, backend, int(timeout.Seconds())) var lastErr error for attempt := 0; attempt <= maxRetries; attempt++ { + // Check for cancellation before retrying + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + if attempt > 0 { // Exponential backoff: 500ms, 1s, 2s... time.Sleep(retryDelay * time.Duration(1<<(attempt-1))) } - ctx, cancel := context.WithTimeout(context.Background(), timeout) + attemptCtx, cancel := context.WithTimeout(ctx, timeout)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/services/jackett/client.go(5 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/services/jackett/client.go
🧬 Code graph analysis (1)
internal/services/jackett/client.go (3)
web/src/types/index.ts (1)
JackettIndexer(1197-1205)internal/models/torznab_indexer.go (3)
TorznabBackendProwlarr(35-35)TorznabBackendJackett(33-33)TorznabBackend(29-29)pkg/prowlarr/client.go (2)
Indexer(76-84)NewClient(46-73)
⏰ 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 (4)
internal/services/jackett/client.go (4)
493-540: LGTM!The two-pass approach (build list → fetch caps in parallel → apply results) is clean and correctly modifies slice elements using index-based iteration.
559-592: LGTM!Good optimization to only fetch capabilities for configured indexers, keeping the parallel pattern consistent with the Prowlarr path.
601-672: LGTM!The semaphore-based concurrency limiting with
WaitGroup+ channel close pattern is idiomatic Go. Results map is safely written only from the main goroutine after channel receives, avoiding races.The approach of spawning all goroutines upfront (blocking on semaphore) is fine for typical indexer counts. For extreme scales, a worker pool would be more memory-efficient, but that's not a concern here.
701-711: LGTM!The method correctly delegates to the underlying jackett client which was initialized with a timeout, so requests won't hang indefinitely.
7af946d to
cb32cbd
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
web/src/components/indexers/AutodiscoveryDialog.tsx (1)
120-147: Categories sent on update will currently be dropped by the APIPassing
categories: indexer.categoriesin bothupdateDataandcreateDatais the right shape for the new feature, but note that theUpdateIndexerhandler (internal/api/handlers/jackett.go) doesn’t currently define or persist aCategoriesfield—onlyCapabilities. With capabilities present, the handler also skipsSyncIndexerCaps, so existing indexers imported here won’t get categories persisted at all. Once the backend is updated to accept and storecategorieson update (mirroring the create path), this UI code will behave as intended.internal/api/handlers/jackett.go (1)
497-589: UpdateIndexer ignores categories and can never persist them for existing indexers
UpdateIndexer’s request struct and post‑update logic only deal withCapabilities; there’s noCategoriesfield and no call toSetCategories. With the new Autodiscovery flow, updates send both capabilities and categories:
- JSON
categoriesis silently ignored byencoding/jsonbecause the struct has no matching field.- Because
Capabilitiesis non‑empty, the handler stores only caps and then skipsSyncIndexerCaps, so categories are neither stored from the payload nor refreshed via the service.Result: existing indexers imported via Autodiscovery never get categories persisted, defeating the purpose of the new category plumbing for updates.
You can fix this by mirroring the create logic in
UpdateIndexer:@@ func (h *JackettHandler) UpdateIndexer(w http.ResponseWriter, r *http.Request) { - var req struct { - Name string `json:"name"` - BaseURL string `json:"base_url"` - IndexerID *string `json:"indexer_id"` - APIKey string `json:"api_key"` - Backend *string `json:"backend"` - Enabled *bool `json:"enabled"` - Priority *int `json:"priority"` - TimeoutSeconds *int `json:"timeout_seconds"` - Capabilities []string `json:"capabilities,omitempty"` - } + var req struct { + Name string `json:"name"` + BaseURL string `json:"base_url"` + IndexerID *string `json:"indexer_id"` + APIKey string `json:"api_key"` + Backend *string `json:"backend"` + Enabled *bool `json:"enabled"` + Priority *int `json:"priority"` + TimeoutSeconds *int `json:"timeout_seconds"` + Capabilities []string `json:"capabilities,omitempty"` + Categories []models.TorznabIndexerCategory `json:"categories,omitempty"` + } @@ func (h *JackettHandler) UpdateIndexer(w http.ResponseWriter, r *http.Request) { - // If capabilities were provided in the request, store them directly - if len(req.Capabilities) > 0 { - if err := h.indexerStore.SetCapabilities(r.Context(), indexer.ID, req.Capabilities); err != nil { - log.Warn(). - Err(err). - Int("indexer_id", indexer.ID). - Str("indexer", indexer.Name). - Msg("Failed to store provided capabilities") - } - // Reload indexer to include the stored capabilities - if updated, err := h.indexerStore.Get(r.Context(), indexer.ID); err == nil { - indexer = updated - } - } else if h.service != nil { - // No capabilities provided, try to fetch them from the service - if updated, err := h.service.SyncIndexerCaps(r.Context(), indexer.ID); err != nil { - log.Warn(). - Err(err). - Int("indexer_id", indexer.ID). - Str("indexer", indexer.Name). - Msg("Failed to sync torznab caps after update") - } else if updated != nil { - indexer = updated - } - } + // If capabilities or categories were provided in the request, store them directly + if len(req.Capabilities) > 0 || len(req.Categories) > 0 { + if len(req.Capabilities) > 0 { + if err := h.indexerStore.SetCapabilities(r.Context(), indexer.ID, req.Capabilities); err != nil { + log.Warn(). + Err(err). + Int("indexer_id", indexer.ID). + Str("indexer", indexer.Name). + Msg("Failed to store provided capabilities") + } + } + if len(req.Categories) > 0 { + if err := h.indexerStore.SetCategories(r.Context(), indexer.ID, req.Categories); err != nil { + log.Warn(). + Err(err). + Int("indexer_id", indexer.ID). + Str("indexer", indexer.Name). + Msg("Failed to store provided categories") + } + } + // Reload indexer to include the stored capabilities and categories + if updated, err := h.indexerStore.Get(r.Context(), indexer.ID); err == nil { + indexer = updated + } + } else if h.service != nil { + // No capabilities/categories provided, try to fetch them from the service + if updated, err := h.service.SyncIndexerCaps(r.Context(), indexer.ID); err != nil { + log.Warn(). + Err(err). + Int("indexer_id", indexer.ID). + Str("indexer", indexer.Name). + Msg("Failed to sync torznab caps after update") + } else if updated != nil { + indexer = updated + } + }This makes the update behaviour consistent with create and ensures Autodiscovery updates can persist both caps and categories.
🧹 Nitpick comments (1)
web/src/types/index.ts (1)
1096-1120: Form/update types now carry full category objects; confirm backend ignores client indexer_idUsing
TorznabIndexerCategoryfor bothTorznabIndexerFormData.categoriesandTorznabIndexerUpdate.categoriesis consistent with the backend model, but it means create/update payloads can carry anindexer_idthat doesn’t match the actual DB id. As long asSetCategoriesuses the path/indexer argument as the source of truth and overwrites/ignoresindexer_idfrom the slice, this is fine; otherwise, it may be safer long‑term to introduce a narrower input type withoutindexer_id.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
internal/api/handlers/jackett.go(2 hunks)internal/services/jackett/client.go(6 hunks)web/src/components/indexers/AutodiscoveryDialog.tsx(2 hunks)web/src/types/index.ts(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/services/jackett/client.go
🧰 Additional context used
🧠 Learnings (1)
📓 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.
🧬 Code graph analysis (2)
web/src/types/index.ts (1)
internal/models/torznab_indexer.go (1)
TorznabIndexerCategory(102-107)
internal/api/handlers/jackett.go (2)
web/src/types/index.ts (1)
TorznabIndexerCategory(986-991)internal/models/torznab_indexer.go (1)
TorznabIndexerCategory(102-107)
⏰ 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/types/index.ts (1)
1199-1208: Exposing categories on JackettIndexer matches new discovery flowAdding
categories?: TorznabIndexerCategory[]toJackettIndexeraligns the discovery response with the new create/update payloads and lets the UI forward Jackett‑derived categories end‑to‑end. No issues from a typing perspective.internal/api/handlers/jackett.go (2)
327-338: CreateIndexer request payload extension for capabilities/categories looks consistentThe extended request struct (adding
Capabilities []stringandCategories []models.TorznabIndexerCategory) matches the JSON contract used by the UI types and keeps create‑time validation logic intact. Field names and types line up withTorznabIndexerCategoryand the existing caps model.
405-440: Create path correctly prefers provided caps/categories over service syncThe new post‑create branch cleanly handles three cases: (1) persist provided capabilities, (2) persist provided categories, and (3) when neither is provided and
serviceis non‑nil, fall back toSyncIndexerCaps. This avoids an unnecessary network round‑trip when the client already has fresh data (e.g., via discovery) while preserving the old behaviour when nothing is supplied. Logging failures as warnings instead of failing the whole create is consistent with the prior caps logic.
…scovery - Change log level from Debug to Warn for individual indexer capability fetch failures so they're visible in production logs - Propagate request context through discovery functions to enable proper cancellation when HTTP request is aborted - Use select-based backoff delays to allow interruption during retries
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (4)
internal/api/handlers/jackett.go (2)
327-338: Guard against client-suppliedIndexerIDinCategories.The CreateIndexer payload reuses
models.TorznabIndexerCategory, which includes anIndexerIDfield, while you also passindexer.IDseparately intoSetCategories. To avoid ever trusting client-supplied relationships (and to prevent leaking any remote Jackett/Prowlarr IDs into your local schema), make sureSetCategoriesoverwrites or ignorescategory.IndexerIDand uses theindexerIDargument as the single source of truth. If that’s already the case, a future cleanup could be to introduce a slimmer API DTO withoutIndexerIDto decouple wire format from storage model.
405-440: Capabilities/categories persistence is best‑effort; consider whether silent partial failure is acceptable.When either
SetCapabilitiesorSetCategoriesfails, you only log a warning and still return201with whatever the store currently has. That keeps creation robust, but the client won’t know caps/categories were dropped even though it explicitly sent them. If caps/categories are important for UX (e.g. cross‑seed behaviour), consider failing the request when explicit values are provided but persistence fails, or at least returning some flag/message indicating partial success.internal/services/jackett/client.go (2)
471-545: Context-aware discovery and shared caps pipeline look solid; consider a small ctx guard.The split between
DiscoverJackettIndexersanddiscoverJackettIndexersis clean, and feeding both Jackett and Prowlarr discovery through the same parallel caps application keeps behaviour consistent forCaps/Categories. Using the incomingctxfor bothGetIndexersand the subsequent caps fetch is exactly what you want for cancellation.One minor defensive improvement: if any non-HTTP callers ever pass a nil context, both discovery flows will pass that nil into downstream calls. To avoid surprises, you could normalize at the top:
func DiscoverJackettIndexers(ctx context.Context, baseURL, apiKey string) ([]JackettIndexer, error) { - if baseURL = strings.TrimSpace(baseURL); baseURL == "" { + if ctx == nil { + ctx = context.Background() + } + if baseURL = strings.TrimSpace(baseURL); baseURL == "" { return nil, fmt.Errorf("base url is required") }(and similarly inside
discoverJackettIndexersif you want extra belt-and-suspenders).Also applies to: 547-594
14-18: Parallel caps fetcher and retry logic look correct; you might want to reuse the client per backend.The concurrency control and cancellation behaviour in
fetchCapsParallel/fetchCapsWithRetrylook good:
- Bounded parallelism via the
semchannel.- Per-attempt timeouts layered on top of the parent
ctx.- Proper
WaitGroup+resultsChanclosing, with all map writes confined to the collector goroutine, so no races.- Partial failures are logged and don’t abort discovery, matching the comment.
The only tweak I’d consider is avoiding
NewClientper indexer infetchCapsWithRetry. For many indexers this will spin up multiplehttp.Client/transport instances against the samebaseURL. Reusing a singleClientper(baseURL, backend)withinfetchCapsParallel(or passing one in) would preserve connection pooling and reduce allocations without changing behaviour.Also applies to: 596-683, 685-720
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/api/handlers/jackett.go(3 hunks)internal/services/jackett/client.go(5 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/jackett.gointernal/services/jackett/client.go
🧬 Code graph analysis (1)
internal/api/handlers/jackett.go (3)
web/src/types/index.ts (1)
TorznabIndexerCategory(986-991)internal/models/torznab_indexer.go (1)
TorznabIndexerCategory(102-107)internal/services/jackett/client.go (1)
DiscoverJackettIndexers(473-545)
⏰ 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 (2)
internal/api/handlers/jackett.go (1)
817-817: Good: discovery now respects HTTP request cancellation.Wiring
r.Context()intojackett.DiscoverJackettIndexersensures the parallel caps/category fetches get cancelled promptly when the HTTP client disconnects or the server times out, instead of running to completion in the background.internal/services/jackett/client.go (1)
461-468: JackettIndexer struct extension for categories looks consistent.Adding
Categories []models.TorznabIndexerCategoryalongsideCaps []stringcleanly exposes both capabilities and category metadata to callers, and the JSON shape aligns with the sharedTorznabIndexerCategorymodel/frontend type.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
internal/api/handlers/jackett.go (1)
566-601: Duplicate comment - see lines 405-440.This is the same duplicated storage logic referenced in the earlier comment.
🧹 Nitpick comments (3)
internal/api/handlers/jackett.go (1)
405-440: Consider consolidating duplicate capability/category storage logic into a helper function.The logic for storing capabilities and categories (lines 405-440 and 566-601) is duplicated between
CreateIndexerandUpdateIndexer. This pattern of setting capabilities, logging warnings on failure, setting categories, logging warnings on failure, and then reloading the indexer is repeated verbatim.Consider extracting this into a helper method to reduce duplication:
// storeCapsAndCategories stores capabilities and categories if provided, logs warnings on failures, // and reloads the indexer to reflect updates. Returns the updated indexer or the original on reload failure. func (h *JackettHandler) storeCapsAndCategories(ctx context.Context, indexer *models.TorznabIndexer, capabilities []string, categories []models.TorznabIndexerCategory) *models.TorznabIndexer { if len(capabilities) == 0 && len(categories) == 0 { return indexer } if len(capabilities) > 0 { if err := h.indexerStore.SetCapabilities(ctx, indexer.ID, capabilities); err != nil { log.Warn(). Err(err). Int("indexer_id", indexer.ID). Str("indexer", indexer.Name). Msg("Failed to store provided capabilities") } } if len(categories) > 0 { if err := h.indexerStore.SetCategories(ctx, indexer.ID, categories); err != nil { log.Warn(). Err(err). Int("indexer_id", indexer.ID). Str("indexer", indexer.Name). Msg("Failed to store provided categories") } } // Reload indexer to include the stored capabilities and categories if updated, err := h.indexerStore.Get(ctx, indexer.ID); err == nil { return updated } return indexer }Then both handlers would simplify to:
if len(req.Capabilities) > 0 || len(req.Categories) > 0 { indexer = h.storeCapsAndCategories(r.Context(), indexer, req.Capabilities, req.Categories) } else if h.service != nil { // No capabilities/categories provided, try to fetch them from the service // ... existing sync logic }internal/services/jackett/client.go (2)
614-619: Consider making concurrency and retry parameters configurable.The hardcoded constants for
maxConcurrent,maxRetries,retryDelay, andfetchTimeoutmay not be optimal for all environments. For example:
- Some Jackett/Prowlarr instances may handle more than 10 concurrent requests
- Network conditions may require different retry strategies
- Timeout values may need adjustment for slow indexers
Consider adding these as configuration options or at minimum, document the rationale for these values:
const ( // maxConcurrent limits parallel capability fetches to avoid overwhelming the backend. // This value balances discovery speed with backend resource constraints. maxConcurrent = 10 // maxRetries is the number of retry attempts per indexer. // With exponential backoff, this provides ~3.5 seconds of retry time. maxRetries = 2 // retryDelay is the initial delay between retries. // Exponential backoff doubles this for each subsequent attempt. retryDelay = 500 * time.Millisecond // fetchTimeout is the maximum time to wait for a single capability fetch attempt. fetchTimeout = 15 * time.Second )
633-639: Context cancellation check may introduce unnecessary overhead.The explicit context cancellation check before acquiring the semaphore (lines 634-639) may not be necessary since:
- The semaphore acquisition itself doesn't block if the context is already cancelled
- The
fetchCapsWithRetryfunction already checksctx.Err()at multiple points- The goroutine will exit quickly anyway if the context is cancelled
However, this is a minor performance consideration and the current defensive approach is valid.
If you want to optimize, you could remove the explicit check:
- // Check if parent context is already cancelled - select { - case <-ctx.Done(): - resultsChan <- capsFetchResult{indexerID: indexerID, err: ctx.Err()} - return - default: - } - // Acquire semaphore sem <- struct{}{} defer func() { <-sem }()The function will still handle cancellation correctly via the context passed to
fetchCapsWithRetry.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/api/handlers/jackett.go(5 hunks)internal/services/jackett/client.go(5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 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/services/jackett/client.gointernal/api/handlers/jackett.go
🧬 Code graph analysis (2)
internal/services/jackett/client.go (2)
internal/models/torznab_indexer.go (4)
TorznabBackend(29-29)TorznabIndexerCategory(102-107)TorznabBackendProwlarr(35-35)TorznabBackendJackett(33-33)web/src/types/index.ts (2)
TorznabIndexerCategory(986-991)JackettIndexer(1199-1208)
internal/api/handlers/jackett.go (3)
web/src/types/index.ts (1)
TorznabIndexerCategory(986-991)internal/models/torznab_indexer.go (1)
TorznabIndexerCategory(102-107)internal/services/jackett/client.go (1)
DiscoverJackettIndexers(473-548)
⏰ 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 (8)
internal/api/handlers/jackett.go (3)
328-337: LGTM - Categories field properly added to CreateIndexer request.The addition of the
Categoriesfield to the request struct is consistent with the existingCapabilitiesfield and matches the expected type from the models package.
497-507: LGTM - Categories field properly added to UpdateIndexer request.The addition of the
Categoriesfield is consistent with the CreateIndexer request struct and matches the expected type.
829-829: LGTM - Context propagation enables proper cancellation.Passing
r.Context()toDiscoverJackettIndexersallows the discovery operation to be cancelled if the HTTP request is cancelled, preventing resource waste on abandoned requests.internal/services/jackett/client.go (5)
14-18: LGTM - Imports properly added for new functionality.The
syncpackage is needed forsync.WaitGroupin the parallel cap fetching, andzerolog/logis needed for the new logging statements.
461-468: LGTM - Categories field properly added to JackettIndexer.The
Categoriesfield is correctly typed as[]models.TorznabIndexerCategoryand includes theomitemptyJSON tag, consistent with theCapsfield.
471-548: LGTM - Context support and parallel cap fetching improve reliability and performance.The changes enhance the discovery process by:
- Supporting context cancellation throughout the discovery flow
- Fetching capabilities in parallel with retries for all indexers
- Handling partial failures gracefully without failing the entire discovery
550-597: LGTM - Jackett discovery path now matches Prowlarr implementation.The refactoring to use parallel cap fetching for Jackett indexers is consistent with the Prowlarr path and provides the same benefits.
694-702: Exponential backoff implementation is correct and well-designed.The retry logic properly implements:
- Exponential backoff with
1<<(attempt-1)multiplier- Context cancellation awareness during sleep
- Early exit on cancellation
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
internal/services/jackett/client.go (1)
599-686: Parallel caps fetch implementation is sound; consider toning down logging on pure cancellations
fetchCapsParallelcorrectly:
- Short‑circuits on empty
indexerIDs.- Limits concurrency via a semaphore while still fan‑out/fan‑in on a buffered results channel.
- Uses the parent
ctxfor both early bailout and for the retry helper, then aggregates per‑indexer errors plus a summary.One optional refinement: when
result.erris purely due to context cancellation or deadline (e.g. the HTTP request was cancelled by the caller), you may want to log atDebugorInforather thanWarn, or skip the per‑indexer warning altogether, to avoid noisy logs on expected user cancellations.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/services/jackett/client.go(5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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/services/jackett/client.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/services/jackett/client.go
🧬 Code graph analysis (1)
internal/services/jackett/client.go (2)
internal/models/torznab_indexer.go (3)
TorznabBackend(29-29)TorznabIndexerCategory(102-107)TorznabBackendJackett(33-33)web/src/types/index.ts (2)
TorznabIndexerCategory(986-991)JackettIndexer(1199-1208)
⏰ 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 (4)
internal/services/jackett/client.go (4)
461-468: JackettIndexer shape and JSON tags look consistent with UI typesThe struct layout and JSON tags for
ID,Name,Description,Type,Configured,Backend,Caps, andCategoriesline up with the TSJackettIndexerinterface and should serialize cleanly to the UI.
471-548: Context-aware discovery flow and parallel caps fetch wiring look correct
DiscoverJackettIndexersnow:
- Safely defaults
ctxtocontext.Background()when nil.- Reuses the new
discoverJackettIndexershelper for Jackett, keeping Prowlarr fallback behavior intact.- For the Prowlarr path, builds
indexersandindexerIDs, then usesfetchCapsParalleland cleanly appliesCaps/Categoriesback onto the slice.The partial‑failure semantics (log + continue) are reasonable for discovery and won’t break callers that only need the base indexer list even when some caps fail.
550-597: Jackett discovery helper correctly scopes caps fetch to configured indexers
discoverJackettIndexers:
- Uses
GetIndexersCtx(ctx)so discovery is cancelable.- Builds
JackettIndexervalues for all indexers while restricting caps fetch to configured ones viaconfiguredIDs.- Applies
CapsandCategoriesonly when an entry exists incapsMap, which degrades gracefully when individual fetches fail.This separation of indexer enumeration from caps fetching keeps the flow clear and avoids unnecessary work for unconfigured indexers.
688-723: Retry helper behavior and context usage look correct
fetchCapsWithRetry:
- Constructs a backend‑appropriate
Clientonce and reuses it across attempts.- Implements bounded retries with exponential backoff that honor the parent
ctx(bailing early if cancelled).- Wraps each attempt in
context.WithTimeoutto cap individual request duration and deferscancel()immediately after creation, which avoids leaks even ifFetchCapspanics or returns early, with at mostmaxRetries+1outstanding defers.Overall the retry logic and context handling are correct and safe for the small fixed retry count used here.
… caps fetch - Remove defer cancel() from retry loop to prevent context accumulation - Add select-based semaphore acquisition with context cancellation support
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
internal/services/jackett/client.go (1)
693-728: Consider deferringcancel()immediately aftercontext.WithTimeoutin the retry loop.Within
fetchCapsWithRetry,attemptCtx, cancel := context.WithTimeout(ctx, timeout)followed by an explicitcancel()afterFetchCapsworks in the normal path, but ifclient.FetchCapswere ever to panic, the cancel would not run and the timer resources would live until timeout. A more idiomatic pattern is:attemptCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() caps, err := client.FetchCaps(attemptCtx, indexerID)Given the small, bounded number of retries, the cost of a few defers is negligible and ensures cleanup even on panics or early returns.
🧹 Nitpick comments (2)
internal/services/jackett/client.go (2)
550-597: Jackett discovery + parallel caps fetch for configured indexers is well-structured.Using
GetIndexersCtx(ctx)keeps discovery cancellable, and only configured Jackett indexers are queued for caps fetch, which is efficient. ApplyingcapsMapback onto the fullindexersslice keeps the API backward compatible while enriching the data. If you expectdiscoverJackettIndexersto ever be called directly with a nil ctx, consider normalizing ctx at the top of this function as well, mirroringDiscoverJackettIndexers(optional).
599-691: Parallel caps fetching pattern looks safe; a couple of small robustness nits (optional).The semaphore + WaitGroup + channel fan-in pattern is sound, and all goroutines either short-circuit on
ctx.Done()or send exactly onecapsFetchResult, so there’s no leak. Minor polish you could consider:
- Preallocate
resultswithlen(indexerIDs)to avoid map growth.- At the top of
fetchCapsParallel, normalize a nilctxtocontext.Background()so this helper is robust even if used outside the current call sites.
Also, you might want to avoid logging atWarnlevel for expectedcontext.Canceledcases to reduce noise in busy systems.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/services/jackett/client.go(5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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/services/jackett/client.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/services/jackett/client.go
🧬 Code graph analysis (1)
internal/services/jackett/client.go (4)
internal/models/torznab_indexer.go (4)
TorznabBackend(29-29)TorznabIndexerCategory(102-107)TorznabBackendProwlarr(35-35)TorznabBackendJackett(33-33)web/src/types/index.ts (2)
TorznabIndexerCategory(986-991)JackettIndexer(1199-1208)pkg/prowlarr/client.go (3)
NewClient(46-73)Config(21-28)Indexer(76-84)pkg/gojackett/jackett.go (2)
NewClient(47-76)Config(26-45)
⏰ 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 (2)
internal/services/jackett/client.go (2)
459-468: JackettIndexer extension (Backend, Caps, Categories) is consistent with models and UI contracts.Struct fields and JSON tags align with
models.TorznabBackendandTorznabIndexerCategoryplus the frontendJackettIndexertype; this should round-trip cleanly across API and UI.
471-548: Context-aware discovery and Prowlarr caps enrichment flow look correct.Nil context is normalized to
context.Background, Jackett is tried first, then Prowlarr as a fallback; disabled/non-torrent indexers are skipped, and Prowlarr IDs are consistently stringified for both discovery and caps fetching. The parallel caps enrichment for Prowlarr indexers viafetchCapsParallelis wired in cleanly.
…/update Add IndexerResponse type with optional warnings field to surface partial failures (e.g., capabilities/categories storage errors) to the frontend. Previously these errors were only logged server-side. Frontend now displays warning toasts when the API returns warnings, informing users to sync caps manually if needed.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
internal/api/handlers/jackett.go (2)
30-34: IndexerResponse + create flow behave correctly; consider surfacing sync failures as warnings and updating docsThe new
IndexerResponsewrapper and create flow correctly:
- persist provided capabilities/categories when present, appending warnings on partial failures, and
- fall back to
SyncIndexerCapswhen nothing is provided.Two non-blocking improvements to consider:
- When
SyncIndexerCapsfails (in theelse if h.service != nilbranch), you currently only log; adding a warning like"Failed to sync capabilities/categories - sync manually later"would give clients consistent feedback for partial failures.- The swagger
@Successannotation forCreateIndexerstill referencesmodels.TorznabIndexer, but the handler now returnsIndexerResponse, which may confuse generated docs/clients.Also applies to: 333-454
508-629: Update flow mirrors create correctly; same note on sync warnings and swagger typeThe update handler’s capability/category handling matches the create logic and correctly reloads the indexer and invalidates the search cache; the warning accumulation for partial SetCapabilities/SetCategories failures is sound.
As above, you might:
- also surface a warning when
SyncIndexerCapsfails instead of only logging, and- update the
@Success 200swagger annotation to referenceIndexerResponseinstead ofmodels.TorznabIndexer.web/src/components/indexers/AutodiscoveryDialog.tsx (1)
96-175: Import flow correctly incorporates categories and warning-aware responsesThe import loop now:
- forwards discovered
capsandcategoriesinto update/create payloads, and- counts responses with
warningsto adjust the final toast.This matches the new
IndexerResponsecontract and keeps error handling unchanged; looks good.If you ever want more insight, you could also aggregate and log the actual warning messages per indexer instead of just counting them, but it’s not required for this change set.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
internal/api/handlers/jackett.go(8 hunks)web/src/components/indexers/AutodiscoveryDialog.tsx(6 hunks)web/src/components/indexers/IndexerDialog.tsx(2 hunks)web/src/components/torrents/CrossSeedDialog.tsx(2 hunks)web/src/lib/api.ts(2 hunks)web/src/types/index.ts(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- web/src/types/index.ts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-06T11:59:21.390Z
Learnt from: Audionut
Repo: autobrr/qui PR: 553
File: web/src/components/torrents/TorrentTableOptimized.tsx:1510-1515
Timestamp: 2025-11-06T11:59:21.390Z
Learning: In the qui project, the API layer in web/src/lib/api.ts normalizes backend snake_case responses to camelCase for frontend consumption. For CrossSeed search results, the backend's download_url field is transformed to downloadUrl in the searchCrossSeedTorrent method, so frontend code should always use the camelCase variant (result.downloadUrl).
Applied to files:
web/src/components/torrents/CrossSeedDialog.tsx
📚 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/CrossSeedDialog.tsx
📚 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/jackett.go
🧬 Code graph analysis (3)
web/src/lib/api.ts (2)
web/src/types/index.ts (2)
TorznabIndexerFormData(1101-1112)IndexerResponse(987-989)internal/api/handlers/jackett.go (1)
IndexerResponse(31-34)
web/src/components/indexers/IndexerDialog.tsx (1)
web/src/lib/api.ts (1)
api(1655-1655)
web/src/components/indexers/AutodiscoveryDialog.tsx (1)
web/src/lib/api.ts (1)
api(1655-1655)
⏰ 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 (6)
web/src/components/torrents/CrossSeedDialog.tsx (2)
39-39: LGTM! Clean import addition.The
ExternalLinkicon is properly imported from lucide-react to support the visual enhancement below.
375-380: Excellent UX improvement with proper implementation.The external link icon addition enhances user experience by providing a clear visual indicator for external links. The implementation is well-crafted:
inline-flexwithitems-centerensures proper vertical alignmentgap-1provides balanced spacing between text and iconshrink-0on the icon prevents compression in constrained layoutsopacity-50keeps the icon subtle and non-intrusive- Wrapping the title in a
<span>preserves text truncation while keeping the icon visibleweb/src/components/indexers/IndexerDialog.tsx (1)
87-92: Indexer create/update warning handling looks solidUsing the new
IndexerResponse.warningsto choose between warning and success toasts is correct and keeps the happy-path unchanged; nothing blocking here.Also applies to: 112-117
internal/api/handlers/jackett.go (1)
845-853: Good use ofr.Context()in discoveryPassing
r.Context()intojackett.DiscoverJackettIndexersis a nice improvement for cancellation/timeout propagation during discovery.web/src/components/indexers/AutodiscoveryDialog.tsx (1)
239-241: Input autofill/1Password suppression changes are safeDisabling autocomplete and adding
data-1p-ignoreon the URL and API key fields is low-risk and improves control over password-manager behavior.Also applies to: 260-262
web/src/lib/api.ts (1)
57-61: API return type switch toIndexerResponseis appropriateUpdating
createTorznabIndexer/updateTorznabIndexerto returnIndexerResponsealigns the client with the backend wrapper and enables warning handling without breaking JSON shape. All call sites inIndexerDialog.tsxandAutodiscoveryDialog.tsxcorrectly access theresponse.warningsproperty.
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
Performance Improvements
New Features
UI
✏️ Tip: You can customize this high-level summary in your review settings.