Torrents: surface the stall settings as UI controls (noldevin)

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.
This commit is contained in:
BoulderBadgeDad 2026-06-07 12:52:57 -07:00
parent 5187fe5f66
commit 46f03827a2
2 changed files with 34 additions and 0 deletions

View file

@ -5349,6 +5349,23 @@
Override where the torrent client writes downloads. This path is on the <strong>torrent client's</strong> machine, not SoulSync's.
</div>
</div>
<div class="form-group">
<label>Stalled torrent timeout (minutes):</label>
<input type="number" id="torrent-stall-timeout" min="0" step="1" placeholder="10">
<div class="setting-help-text">
Give up on a torrent that makes <strong>zero download progress</strong> 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. <strong>0 disables</strong> (wait the full limit).
</div>
</div>
<div class="form-group">
<label>When a torrent stalls:</label>
<select id="torrent-stall-action" class="form-select">
<option value="abandon">Abandon — remove it &amp; its partial data, fail the download</option>
<option value="pause">Pause — pause it in the client, leave it for me</option>
</select>
<div class="setting-help-text">
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.
</div>
</div>
<div class="form-group">
<label>Status:</label>
<div class="form-actions" style="margin-top: 4px;">

View file

@ -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',