Repository navigation
feat(indexer): per indexer seed time and inactive seed time limits (#2206) - #2917
Conversation
…2206) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
…2206) Adds seedTimeMinutes and inactiveSeedTimeMinutes to indexers next to the seed ratio (migration 097), with a seed_time_source provenance column so a Prowlarr sync fills the seed time from torrentBaseSettings.seedTime without overwriting a value the user set or cleared. The provenance is separate from the ratio's because any edit marks the ratio user owned, which would otherwise keep Prowlarr from ever filling the new field on existing indexers. The grab carries both limits to the client: qBittorrent gets all three in one setShareLimits call (unset ones stay -2), Transmission gets the inactive limit as seedIdleLimit, and Deluge and rTorrent, which have no per torrent time limits, log at debug and keep their own rules. Transmission's torrent-add ignores seedRatioLimit and seedRatioMode (only torrent-set reads them, see rpcimpl.cc), so the per indexer ratio never applied there. Ratio and idle limit now go out in a torrent-set after the add, addressed by info hash, and a failure is logged rather than failing the grab. The API rejects a seed time below 1 minute or above ten years; null clears it. The indexer form gains both fields with per client help text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
There was a problem hiding this comment.
Overall this is a clean, well-reasoned change. The separate provenance column for seed time is the right call — sharing it with seed_ratio_source would freeze every previously-edited indexer out of Prowlarr auto-population. The Transmission ratio fix is a genuine bug fix backed by the libtransmission source.
One behavioral concern: Transmission idle-time clamping is logged at debug
internal/downloader/transmission/client.go — applySeedIdleArgs:
if v > maxSeedIdleMinutes {
slog.Debug("transmission: inactive seed time clamped to the daemon's maximum", ...)
v = maxSeedIdleMinutes
}The API accepts up to 5,256,000 minutes; maxSeedIdleMinutes is 65,535 (~45 days). If a user sets, say, inactiveSeedTimeMinutes: 200000 on a Transmission indexer, they get 65,535 applied with no visible feedback. This is materially different from "limit not supported" (which we log and document): the limit is applied, but at a different value than configured. Debug-level means this is invisible unless the user has debug logging on.
Suggestion: bump to slog.Warn here, or add the Transmission idle-time ceiling to the help text / docs table. The cap is hardware-real (tr_torrentSetIdleLimit is uint16), so there's no workaround — just needs to be surfaced.
Everything else looks good:
- Migration 097: additive, nullable defaults, independent down path. Pre-existing rows read back as "no override" correctly.
- Auth / tenancy: no new endpoints; new fields ride the existing indexer CRUD which is already admin-gated. No cross-user visibility risk.
- Prowlarr syncer: independent provenance per field, both reconciled unconditionally (not short-circuited),
applyProwlarrSeedTimemirrorsapplyProwlarrSeedRatiofaithfully. - qBittorrent:
-2for unset fields is correct; theIsZeroguard before the call avoids a pointlesssetShareLimitson torrents with no override. SeedLimitsFor(nil)is defended; theidx == nilremoval inqueue.gois safe becauseSeedLimitsForhandles it.- Tests: migration shape, repo round-trip, API (omit-keeps / null-clears / reject-invalid), Prowlarr parse table, qBittorrent form fields, Transmission torrent-set path all covered.
— 🤖 Bindery triage bot (automated). Reply to correct me; a human will see it.
Signed-off-by: vavallee <vavallee@protonmail.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Main moved under this branch: Transmission downloads are keyed by info hash (#2711), the importer polls the Transmission audiobook category (#2712), remove on import exists (#2713) and per indexer seed limits apply after add (#2917). Conflict resolution keeps both sides. GetStalledTorrents now keys both Transmission halves of the StallReport by lower-cased info hash, never the session id, and the torrent-get field list carries main's addedDate alongside metadataPercentComplete and peersConnected. Re-checking the logic against current main turned up three things, fixed here: - Stall detection still polled only the Transmission ebook category, so an audiobook magnet was invisible to it. It now polls every category CategoriesToPoll returns, counting a torrent both filters return once so the outage guard's denominator is not doubled. - A no-metadata torrent was removed with delete-local-data. It has no files, so it is now removed entry only (StallKind.DeletesData), the same as remove on import. Client reported stalls still delete data. - The re-search after a no-metadata stall did nothing when the dead release still ranked first: it was approved again and the #2710 cooldown refused the grab instead of trying the runner up. The re-search now carries the stalled GUID and a decision spec skips it for that one search only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
Summary
Closes #2206.
Torrent indexers get two new optional limits next to the seed ratio: seed time (total minutes) and inactive seed time (minutes without upload). They are carried to the download client at grab time, the same way the ratio is.
seed_time_minutes,inactive_seed_time_minutes(nullable, NULL = client's own rule) andseed_time_source.torrentBaseSettings.seedTime(int, minutes) per indexer, so the syncer fills the seed time with the same provenance rules as the ratio. It has its own source column on purpose: every PUT marksseed_ratio_sourceasuser, so sharing it would stop Prowlarr from ever filling the seed time on indexers that have been edited once. Prowlarr has no inactive seed time, so that field is user only.seedTimeMinutes/inactiveSeedTimeMinuteson indexer create and update. Omitted keeps the stored value,nullclears, below 1 or above ten years is a 400.docs/API.md,docs/User-Guide-Wiki.md(support table),docs/QUICKSTART.md(rTorrent note).Found along the way: the Transmission ratio never applied
AddTorrentDetailedputseedRatioLimit/seedRatioModeontorrent-add. Transmission'storrentAddreads none of the seed keys; onlytorrentSetdoes (libtransmission/rpcimpl.cc, checked on 4.0.6, and the RPC spec lists them under torrent-set only). So the per indexer ratio has been a silent no-op on Transmission. Ratio and idle limit now go out in atorrent-setright after the add, addressed by info hash. A failed torrent-set is logged and the grab still succeeds, matching qBittorrent and Deluge. This has its own### Fixedline in the fragment.Client support
setShareLimitscall, unset limits sent as-2(global). Units are minutes: qBit comparesseedingTimeLimit * 60against its seconds counters.torrent-setwithseedIdleLimit+seedIdleMode=1. No total seed time exists in the RPC. Idle limit is a uint16 in the daemon, so values above 65535 are clamped.stop_at_ratio/stop_ratio/remove_at_ratioonlyA time limit the client cannot take is logged at debug (
unappliedSeedLimitsin the adapter) and the client's own rule applies. rTorrent's existing ratio warning is unchanged.Kept localized for #2713: the Transmission
AddTorrent/AddTorrentDetailedsignatures are unchanged (newAddTorrentWithLimitsunderneath), and the adapter change is a few lines per client case.Fail before
Behavioural, on main's Transmission client:
UI tests against main's
IndexersTab.tsx: 6 failed (add/edit payloads missing the fields, fields absent). The Go tests for the new fields (db, api, prowlarr, qbittorrent, adapter, scheduler) fail to compile on main since the fields and methods do not exist.Test plan
go build ./... && go vet ./internal/... ./cmd/...go test ./internal/... ./cmd/... -count=1(58 packages ok)golangci-lint run ./internal/... ./cmd/...(0 issues)TestMigrate097AddsSeedTimeColumns), repo round trip (TestIndexerRepo_SeedTimeLimits)TestIndexerSeedTimeLimits_API)seedTime, provenance table, full sync with independent ratio and time ownershipcd web && npx tsc -b && npx eslint src && npx vitest run(1179 passed; eslint warnings are pre-existing, none in touched files)🤖 Generated with Claude Code
https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9