From 46f03827a29cabc8efd90aac8bd505bd3a09c91d Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Sun, 7 Jun 2026 12:52:57 -0700 Subject: [PATCH] Torrents: surface the stall settings as UI controls (noldevin) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to 5187fe5f, which shipped stall handling as config-only keys. Boulder wanted them user-accessible, so the two knobs now render in the Torrent Client settings section: - "Stalled torrent timeout (minutes)" — number input. Shown in MINUTES for friendliness, stored in SECONDS (download_source.torrent_stall_timeout_ seconds). 0 disables. Blank/NaN falls back to the 10-min default on save. - "When a torrent stalls" — Abandon (default) / Pause select, maps to download_source.torrent_stall_action. Both live under download_source (already in the settings POST allowlist), so no backend change — load converts seconds→minutes, save converts back. Inputs/selects only (no onclick), so the script-split onclick-coverage test stays green. settings.js syntax-checked via Windows node. --- webui/index.html | 17 +++++++++++++++++ webui/static/settings.js | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/webui/index.html b/webui/index.html index fb7eff39..bc48facd 100644 --- a/webui/index.html +++ b/webui/index.html @@ -5349,6 +5349,23 @@ Override where the torrent client writes downloads. This path is on the torrent client's machine, not SoulSync's. +
+ + +
+ Give up on a torrent that makes zero download progress for this long — a dead magnet stuck on "downloading metadata", or a swarm with no seeders. Without this, a dud torrent ties up a download slot for the full 6-hour limit. 0 disables (wait the full limit). +
+
+
+ + +
+ Abandon frees the slot so the next source can try. Pause keeps the torrent in your client so you can inspect or resume it manually. +
+
diff --git a/webui/static/settings.js b/webui/static/settings.js index 09e72c3c..380a0b09 100644 --- a/webui/static/settings.js +++ b/webui/static/settings.js @@ -1128,6 +1128,15 @@ async function loadSettingsData() { if (_tcPass) _tcPass.value = settings.torrent_client?.password || ''; if (_tcCat) _tcCat.value = settings.torrent_client?.category || 'soulsync'; if (_tcPath) _tcPath.value = settings.torrent_client?.save_path || ''; + // Stalled-torrent knobs live under download_source but render in the + // torrent client section. Timeout is stored in SECONDS, shown in MINUTES. + const _tcStall = document.getElementById('torrent-stall-timeout'); + const _tcStallAct = document.getElementById('torrent-stall-action'); + if (_tcStall) { + const secs = settings.download_source?.torrent_stall_timeout_seconds; + _tcStall.value = (secs === undefined || secs === null) ? 10 : Math.round(Number(secs) / 60); + } + if (_tcStallAct) _tcStallAct.value = settings.download_source?.torrent_stall_action || 'abandon'; const _ucType = document.getElementById('usenet-client-type'); const _ucUrl = document.getElementById('usenet-client-url'); const _ucKey = document.getElementById('usenet-client-api-key'); @@ -2886,6 +2895,14 @@ async function saveSettings(quiet = false) { hybrid_order: getHybridOrder(), stream_source: document.getElementById('stream-source').value, max_concurrent: parseInt(document.getElementById('max-concurrent-downloads').value) || 3, + // Stalled-torrent knobs (rendered in the torrent client section). + // UI is in MINUTES; stored in SECONDS. Blank/NaN → 10 min default; + // 0 stays 0 (disabled). + torrent_stall_timeout_seconds: (() => { + const m = parseInt(document.getElementById('torrent-stall-timeout')?.value, 10); + return (Number.isFinite(m) && m >= 0 ? m : 10) * 60; + })(), + torrent_stall_action: document.getElementById('torrent-stall-action')?.value || 'abandon', }, tidal_download: { quality: document.getElementById('tidal-download-quality').value || 'lossless',