feat(dl): replace Transmission with qBittorrent
qBittorrent's add call answers `Ok.` and nothing else — no hash, no name, no duplicate signal — so arr derives the v1 infohash from the magnet or the `.torrent` bytes before the call and looks the torrent up by it. That also drops Transmission's numeric torrent id: the hash is the only identity now. The reaper needs "stopped because a share limit was reached", and qBittorrent's state field cannot tell that apart from a hand-paused torrent. So the ratio and idle counters are checked against the limits arr set, and a torrent stopped by a global limit reads as still seeding rather than being deleted. The WebUI needs a login, so `ARR_QBITTORRENT_USERNAME` and `ARR_QBITTORRENT_PASSWORD` join the env-only secrets; leaving both unset is valid for an instance that whitelists arr's subnet. Verified against qBittorrent 5 (WebAPI 2.15.1) in a container: it rejects `setShareLimits` without `shareLimitAction`, which 4.x ignores, so it is always sent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
afe167ca7b
commit
ab4f95b166
@@ -17,7 +17,7 @@ export interface Download {
|
||||
}
|
||||
|
||||
/** §9.8: the live snapshot, degrading to nothing rather than breaking the
|
||||
* page it decorates — Transmission or the daemon being down is a fact the
|
||||
* page it decorates — qBittorrent or the daemon being down is a fact the
|
||||
* signal chain already reports, not something this poll repeats. */
|
||||
export async function fetchDownloads(): Promise<Download[]> {
|
||||
try {
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ export interface HealthReport {
|
||||
status: "ok" | "degraded";
|
||||
version: string;
|
||||
prowlarr: Check;
|
||||
transmission: Check;
|
||||
qbit: Check;
|
||||
tmdb: Check;
|
||||
subtitles: SubtitleHealth;
|
||||
}
|
||||
|
||||
+4
-4
@@ -271,7 +271,7 @@ function main() {
|
||||
const checks = {
|
||||
tmdb: moduleRefs("tmdb"),
|
||||
prowlarr: moduleRefs("prowlarr"),
|
||||
transmission: moduleRefs("transmission"),
|
||||
qbit: moduleRefs("qbit"),
|
||||
} as const;
|
||||
|
||||
const traces = {
|
||||
@@ -293,7 +293,7 @@ function main() {
|
||||
}
|
||||
|
||||
// stagger order for the one power-up animation, upstream to downstream
|
||||
const strikeOrder = [checks.tmdb, checks.prowlarr, master, checks.transmission];
|
||||
const strikeOrder = [checks.tmdb, checks.prowlarr, master, checks.qbit];
|
||||
strikeOrder.forEach((refs, index) => {
|
||||
refs.lamp.style.setProperty("--strike", String(index));
|
||||
});
|
||||
@@ -331,7 +331,7 @@ function main() {
|
||||
|
||||
setModule(master, degraded ? "degraded" : "ok", degraded ? "warn" : "ok", report.status);
|
||||
setTrace("master", degraded ? "warn" : "ok");
|
||||
for (const name of ["tmdb", "prowlarr", "transmission"] as const) {
|
||||
for (const name of ["tmdb", "prowlarr", "qbit"] as const) {
|
||||
const check = report[name];
|
||||
setModule(
|
||||
checks[name],
|
||||
@@ -340,7 +340,7 @@ function main() {
|
||||
check.status,
|
||||
check.detail,
|
||||
);
|
||||
if (name !== "transmission") {
|
||||
if (name !== "qbit") {
|
||||
setTrace(name, TONE_BY_STATUS[check.status]);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ export interface SeasonPackState {
|
||||
* the release is blacklisted and every episode it covered reopens as a gap.
|
||||
* Nothing on screen joined those facts, so the season read `0/10` as though
|
||||
* no grab had ever been tried and the operator found out by opening
|
||||
* Transmission.
|
||||
* qBittorrent.
|
||||
*/
|
||||
export interface ImportFailure {
|
||||
release: string;
|
||||
|
||||
+1
-1
@@ -308,7 +308,7 @@ body {
|
||||
grid-area: out;
|
||||
}
|
||||
|
||||
.area-transmission {
|
||||
.area-qbittorrent {
|
||||
grid-area: sink;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user