qBitrr TorrentPolicyManager parity, config, DB indexes, and parity docs#111
qBitrr TorrentPolicyManager parity, config, DB indexes, and parity docs#111
Conversation
Why: - Align Host orchestration with qBitrr TorrentPolicyManager: independent tracker sort vs free-space, pre-sort tracker/tag sync, split-queue topPrio. - Match qBitrr config/env semantics (QBITRR_* aliases, WebUI BehindHttpsProxy, SortTorrents, qBit defaults) and document strict-parity artifacts. - Harden SQLite parity (ArrInstance cleanup + indexes) and API/docs contracts. Key changes: - TorrentPolicyHelper: gating, tag-to-priority map, queue position keys, monitored policy categories. - ProcessTorrentPolicyAsync: PreSortTrackerTagSync, SortManagedTorrentsByTrackerPriority (tag+announce priority, seeding/downloading passes), then free-space. - SeedingService: GetTorrentQueueSortPriorityAsync / GetQueueSortAnnouncePriorityAsync. - TorrentProcessor: skip ApplyTrackerActions when Host owns sync (SortTorrents). - QBittorrentClient: torrent sort + SetTopPriorityAsync; TorrentInfo.Priority. - ConfigurationLoader: env aliases, migrations (SecureCookies), tracker/WebUI fields. - Tests: TorrentPolicyHelperTests, SeedingService queue-sort tests, config tests. - Docs: parity matrix, contract baseline, certification report, environment.md, webui api, supersede parity-review.
…ort Contains - ReadEnv: treat defined TORRENTARR_* (including empty) as blocking QBITRR_* alias - ApplyConfigMigrations: refresh ConfigVersion when needsMigration or any change - IsQueueSeedingForSort: use Ordinal Contains after ToLowerInvariant - Add Load_EmptyTorrentarrEnvBlocksQbitrrAlias test
React 19 requires matching react and react-dom versions; lockfile had 19.2.5 vs 19.2.4.
… cleanup - ValidateAndFillConfig: default LocalAuthEnabled to true with AuthDisabled false (same as GenerateDefaultConfig) to avoid lockout when auth keys are missing. - Run DeleteRowsWithEmptyArrInstance once per database via torrentarr_manual_migrations, not on every startup. - Extend ConfigurationLoaderTests for WebUI auth defaults after fill.
Isolate component-detection submission from build.yml's contents:read default. Grant contents:write and id-token:write required by the dependency submission API. Limit scan to NuGet to match existing CI usage.
- Default LocalAuthEnabled to true on WebUIConfig so new/fallback instances match GenerateDefaultConfig and never require auth with no login method. - Only rewrite Settings.ConfigVersion to ExpectedConfigVersion when the file version is at or below expected; preserve newer forward-compatible versions when ValidateAndFill adds keys. - Add regression test for newer ConfigVersion after validate-and-fill.
…ategories - Set changed when bumping ConfigVersion so Load() writes TOML when no other migration touched the file (avoids repeated migration on every startup). - Cache GetAllMonitoredPolicyCategories on TorrentarrConfig to avoid per-torrent HashSet allocation in the torrent processing loop. - Add regression tests for both behaviors.
…pply Exposes InvalidateMonitoredPolicyCategoriesCache and calls it after /web and /api config saves so the cached HashSet matches updated Arr and qBit categories. Adds a regression test for cache refresh after in-place changes.
…e sort key once - Replace startup-only managed category snapshot with TorrentPolicyHelper.GetAllMonitoredPolicyCategories on each policy pass so runtime config updates match worker IsMonitoredPolicyCategory gating. - Avoid double TorrentQueuePositionSortKey evaluation in free-space ordering and tracker sort currentByPosition.
- Guard MonitoredPolicyCategoriesCache populate/invalidate with a dedicated lock and double-checked locking in GetAllMonitoredPolicyCategories. - Reorder QBittorrentClient.GetTorrentsAsync to (category, sort, cancellationToken); rename ct to cancellationToken and update call sites.
- Prefer cwd .config/config.toml first in search order; new defaults create .config/ with DB and logs alongside - Add GetDataDirectoryPath() for Host/WebUI/Workers data root; unify ConfigReloader with GetDefaultConfigPath - WebUI: fatal exit on config load failure (non-FileNotFound); Host warns when wwwroot SPA bundle is missing - Add GetDataDirectoryPathTests; update ConfigReloader tests for TORRENTARR_CONFIG when file missing - Refresh AGENTS, CLAUDE, and docs for search order and data directory behavior
| } | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Free-space pause logic doesn't update _currentFreeSpace after pausing
Medium Severity
In ProcessSingleTorrentSpaceAsync, the resume branch updates _currentFreeSpace = freeSpaceTest (line 3302) to track remaining space, but the pause branch pauses the torrent without updating _currentFreeSpace. This means the running space counter never reflects freed capacity from pauses, so the loop keeps pausing every active download regardless of how much space has already been reclaimed — instead of stopping once enough torrents are paused.
Reviewed by Cursor Bugbot for commit 782cfda. Configure here.
| .OrderBy(x => x.key.InactiveQueueGroup) | ||
| .ThenBy(x => x.key.Nq) | ||
| .ThenBy(x => x.torrent.AddedOn) | ||
| .Select(x => (x.client, x.torrent))) |
There was a problem hiding this comment.
Free-space manager pauses highest-priority downloads first
Medium Severity
The free-space torrent iteration sorts by TorrentQueuePositionSortKey ascending — active queue position 1 (highest priority) comes first. In ProcessSingleTorrentSpaceAsync, the first downloading torrents encountered while _currentFreeSpace < 0 get paused. This means the most important user-prioritized downloads are paused before lower-priority ones. The old code used OrderBy(AddedOn) (oldest first). To protect high-priority downloads, the sort for the free-space pause loop likely needs to process lowest-priority (highest queue position number) torrents first.
Reviewed by Cursor Bugbot for commit 782cfda. Configure here.
… touch-ups - Add docs/parity/overview.md and contributor-reference.md; update matrix, certification, contract baseline, and index links; shorten parity-review pointer - MkDocs redirects for removed split parity doc paths - Config docs, release process, and README/AGENTS/CLAUDE alignment - Torrent policy helper and WebUI config text/tests; host test factory; Database SchemaParityTests
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
There are 5 total unresolved issues (including 2 from previous reviews).
Bugbot Autofix is ON, but it could not run because on-demand usage is turned off. To enable Bugbot Autofix, turn on on-demand usage and set a spend limit in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f16dfd8. Configure here.
| ("LocalAuthEnabled", false), | ||
| ("AuthDisabled", false), | ||
| // Must match <see cref="GenerateDefaultConfig"/>: when auth is required, local login is the default path. | ||
| ("LocalAuthEnabled", true), |
There was a problem hiding this comment.
Auth default flip may lock out existing users
High Severity
The ValidateAndFillConfig defaults for AuthDisabled changed from true to false and LocalAuthEnabled from false to true. Existing users whose TOML config files lack these keys (relying on old model defaults of AuthDisabled=true) will have AuthDisabled=false and LocalAuthEnabled=true injected on upgrade. Since they likely have no Username/PasswordHash set, authentication is now required but unconfigurable without the setup flow — effectively locking them out of the WebUI if the setup-required redirect doesn't handle this gracefully for every entry point.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit f16dfd8. Configure here.
| public bool AuthDisabled { get; set; } = true; | ||
| public bool AuthDisabled { get; set; } = false; | ||
| /// <summary>When true, WebUI is assumed behind HTTPS reverse proxy and secure cookie behavior is enabled.</summary> | ||
| public bool BehindHttpsProxy { get; set; } = false; |
There was a problem hiding this comment.
Config field BehindHttpsProxy parsed but never consumed
Medium Severity
BehindHttpsProxy is added to the config model, parsed from TOML, serialized, and migrated from SecureCookies, but no code reads it to change cookie or proxy behavior. Both Host and WebUI still hardcode CookieSecurePolicy.SameAsRequest. The SecureCookies migration silently renames the key, destroying the user's original setting, while the replacement field has no effect.
Reviewed by Cursor Bugbot for commit f16dfd8. Configure here.
| var queueTorrents = sortedTorrents.Where(t => queueMembership.GetValueOrDefault(t.Hash) == queueIsSeeding).ToList(); | ||
| foreach (var torrent in queueTorrents.AsEnumerable().Reverse()) | ||
| await client.SetTopPriorityAsync(torrent.Hash, cancellationToken); | ||
| } |
There was a problem hiding this comment.
Tracker sort processes seeding queue before download queue
Medium Severity
In SortManagedTorrentsByTrackerPriorityAsync, the topPrio loop iterates new[] { true, false }, processing the seeding queue before the downloading queue. In qBitrr, downloading torrents are typically prioritized first (download queue sorted, then seeding). Reversing the iteration order means the downloading queue's topPrio calls execute last and may not match the upstream sort behavior.
Reviewed by Cursor Bugbot for commit f16dfd8. Configure here.


Summary
This pull request brings Torrentarr closer to upstream qBitrr behavior for the global TorrentPolicyManager flow (tracker queue sort + free-space policy), tightens config and environment parity, adds SQLite maintenance aligned with qBitrr, and introduces documentation for strict parity tracking.
Why these changes
TorrentPolicyManager equivalence — qBitrr runs a single global worker that can enable tracker sort and free-space independently, runs pre-sort tracker/tag sync before reordering the queue, applies
topPrioseparately for seeding vs downloading queues, then runs free-space logic. Torrentarr previously ran tracker sort only inside the free-space path, so sort never ran when free-space was disabled. The Host now mirrors the intended pipeline and gating.Config / env parity — qBitrr deployments often use
QBITRR_*env overrides. Torrentarr now accepts aliases alongsideTORRENTARR_*, documents them, and adds fields such asSortTorrents,BehindHttpsProxy, andSecureCookies→BehindHttpsProxymigration for WebUI.Queue sort priority — Upstream blends AddTags-derived priority with announce-based tracker priority.
SeedingServiceexposes this for the Host sort step; workers skip duplicateApplyTrackerActionsForTorrentAsyncwhen the Host owns sync (globalSortTorrents), matching qBitrr policy-manager ownership of tracker sync.Database — Remove legacy rows with empty
ArrInstanceand ensureidx_arrinstance_*indexes exist on upgraded databases, consistent with qBitrr schema expectations.Docs — Add
docs/parity/*(matrix, contract baseline, certification report), update environment and WebUI API docs, and mark the old parity review as superseded so contributors have a single place for strict-parity work.Testing
dotnet test --filter "Category!=Live"(pre-commit also runsdotnet buildandwebuiproduction build).Notes / follow-ups
get_effective_qbit_disabled()in qBitrr also considers modes such asSEARCH_ONLY; Torrentarr does not model that flag yet if you need identical gating.Note
Medium Risk
Medium risk because it changes core torrent policy ordering (queue sorting + free-space gating), config resolution/migrations, and performs DB cleanup/index creation on startup, which can affect runtime behavior and existing deployments.
Overview
Implements a qBitrr-style global TorrentPolicyManager pipeline in the Host: optional pre-sort tracker/tag sync, tracker-priority queue reordering (via qBittorrent
topPrioand/torrents/info?sort=priority), and free-space management gated independently by policy flags.Updates config parity: adds
GetDataDirectoryPath()and a revised config search order (prefers./.config/config.toml), acceptsQBITRR_*env var aliases alongsideTORRENTARR_*, bumpsExpectedConfigVersionto6.1.0, and adds migrations/defaults includingWebUI.SecureCookies→WebUI.BehindHttpsProxyplus trackerSortTorrentssupport.Hardens DB parity by adding
ArrInstanceindexes in the EF model and a one-time startup cleanup for rows with blankarrinstance, and updates workers/services to avoid duplicate tracker sync when the Host policy owns it. Docs and WebUI config UI/tooltips are refreshed accordingly, and a parity tracking doc set is added.Reviewed by Cursor Bugbot for commit f16dfd8. Bugbot is set up for automated code reviews on this repo. Configure here.