Skip to content

Add premiumize cache availability + opt-in OnlyDownloadCached gate - #1036

Open
dabonzo wants to merge 4 commits into
rogerfar:mainfrom
dabonzo:premiumize-cache-first
Open

Add premiumize cache availability + opt-in OnlyDownloadCached gate#1036
dabonzo wants to merge 4 commits into
rogerfar:mainfrom
dabonzo:premiumize-cache-first

Conversation

@dabonzo

@dabonzo dabonzo commented Aug 21, 2026

Copy link
Copy Markdown

What this adds

Two related changes for debrid providers, both scoped to torrents.

1. A real GetAvailableFiles for premiumize. Today the premiumize implementation is a stub:

public Task<IList<DebridClientAvailableFile>> GetAvailableFiles(String hash)
{
    return Task.FromResult<IList<DebridClientAvailableFile>>([]);
}

so premiumize always reports nothing as cached. This implements it against premiumize's
/cache/check, giving it the same meaningful availability answer TorBox already provides.

2. An opt-in OnlyDownloadCached provider setting (default false). When enabled, adding a
torrent whose hash is not cached at the provider is rejected at add time instead of being queued.
For Sonarr/Radarr users that rejection is the point: it lets the *arr fall through to the next
release rather than sitting on a torrent the provider will try to leech from peers.

Why

On premiumize a cached torrent completes instantly and costs a fraction of the fair-use budget, while
an uncached one can stall at 0 B/s from 0 peers indefinitely. Without a working cache check there is
no way to tell those apart before committing to the download.

Design notes

  • Off by default — with the setting disabled, behaviour is unchanged. This is additive.
  • Provider-general, not premiumize-only. The gate reads
    settings.Current.Provider.OnlyDownloadCached and asks whichever client is configured. On a
    provider with no availability check it logs a warning and stays inactive rather than rejecting
    everything.
  • Fails open. A provider error or rate-limit lets the add proceed — if the provider is genuinely
    down the download fails on its own. Failing closed would silently reject a whole library.
  • NZB is untouched (torrent.Type != DownloadType.Nzb) — cache checking has no meaning there.
  • Uncached hashes are remembered for 30 minutes, keyed per provider, so an *arr re-searching the
    same release each RSS pass doesn't re-query, and an "uncached" verdict can't carry across a
    provider switch.
  • The premiumize call deliberately uses raw HTTP rather than the Premiumize.NET library: the library
    never inspects HTTP status codes, so a 429 whose body matches no documented phrase would be
    misread as "not cached". Only a well-formed success answer is allowed to say that.

One caveat worth knowing

Rejected torrents are marked errored, so they are only cleaned up when Delete download when in
error
is above 0. The gate logs a warning when that setting is 0, since rejected entries would
otherwise accumulate.

Tests

Adds 18 test methods across three files (PremiumizeDebridClientTest, TorrentsCacheGateTest,
UncachedHashMemoryTest): cache-check parsing, cached/uncached/rate-limited/error responses, the
gate's accept and reject paths with the setting on and off, and the expiry and provider-scoping of
the uncached-hash memory. Full suite green on this branch (243 Service + 37 Web).

Field use

I've been running this for the last 9 days on a Sonarr/Radarr + premiumize setup, as the sole
download client for both protocols. Happy to answer questions about how it behaves in practice.

Overlap with #1014

#1014 (open) touches PremiumizeDebridClient.cs, Torrents.cs and PremiumizeDebridClientTest.cs
the three files this PR changes most. This branch is rebased on current main (e479404) and applies
cleanly there. If you'd rather merge #1014 first, say so and I'll rebase and resolve.

…nabled

An uncached torrent is marked completed-with-error before any transfer is
created, which the qBittorrent-emulation add endpoint reports as the
synchronous "Fails." response - download managers like Sonarr and Radarr
treat that as a rejected release and fall through to the next candidate in
the same search pass. Rejected hashes are remembered for 30 minutes and
answered from memory (with a times-seen counter in the log), since download
managers re-offer the same release on every RSS/search pass. Cache-check
errors fail open; rate limits pause the dequeue as before. NZBs bypass the
gate (no torrent-cache concept).
Two independent adversarial review passes over the branch converged on
the same defects; all confirmed findings fixed:

- OnlyDownloadCached moves from the shared DbSettingsDefaults (where it
  rendered as four checkboxes, three of them dead) to DbSettingsProvider:
  one checkbox, one config key (Provider:OnlyDownloadCached), read where
  the gate actually runs. Provider-level policy, not a per-torrent default.
- The gate only runs when the provider actually implements an availability
  check (new IDebridClient.SupportsAvailabilityCheck, default false;
  Premiumize and TorBox true). Enabling the setting on RealDebrid/
  AllDebrid/DebridLink previously rejected 100% of torrents forever,
  since their GetAvailableFiles are stubs returning empty. Now it logs a
  clear warning and stays inactive.
- The gate runs BEFORE the provider lock (read-only probe; must not
  extend the lock hold for every queued torrent).
- GetAvailableFiles switches from the Premiumize.NET library to raw HTTP:
  the library never inspects HTTP status codes, so a 429 with an
  unrecognized body escaped rate-limit classification. Error answers now
  THROW (gate fails open) instead of reading as "not cached"; only a
  well-formed success may reject.
- AddQueued replaces a dead pre-provider row (completed-with-error,
  never added) instead of returning it - a re-offered release now gets a
  real re-check instead of answering "Fails." from the stale row forever.
- Error-cleanup only deletes local files for torrents that reached the
  provider (a rejected torrent never downloaded anything; a recursive
  name-match delete could hit unrelated content).
- A human Retry forgets the hash from the uncached-memory so the re-add
  reaches the provider.
- Uncached-memory entries are provider-scoped and pruned opportunistically
  above a size threshold; entry is a readonly record struct per house
  pattern. Warning logged when rejecting with DeleteOnError=0.
- New composed gate test: real Torrents + real PremiumizeDebridClient over
  scripted HTTP - rejected-without-transfer, added-when-cached, and
  answered-from-memory paths.

279 tests green (was 261 at branch point).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant