Skip to content

Automove slow torrent to the end of the download queue#23546

Open
yxrpsrazfyg wants to merge 2 commits intoqbittorrent:masterfrom
yxrpsrazfyg:slow
Open

Automove slow torrent to the end of the download queue#23546
yxrpsrazfyg wants to merge 2 commits intoqbittorrent:masterfrom
yxrpsrazfyg:slow

Conversation

@yxrpsrazfyg
Copy link

@yxrpsrazfyg yxrpsrazfyg commented Nov 26, 2025

#23476

How to Enable

OptionsBitTorrent"Move slow downloading torrents to queue end"
Configure:

  • Time: Duration to monitor torrent progress (in minutes)
  • Minimum size: Minimum download progress required within the time window (in MiB)
  • Excluded tag: Optional tag to exclude specific torrents from detection

Functionality Added

  • Monitors downloading torrents over a configurable time window
  • Automatically detects torrents making insufficient progress
  • Moves slow torrents to the end of the download queue, allowing faster torrents to take priority
  • Provides fine-grained control through configurable parameters

Files Changed

Backend (Core Logic)

  • src/base/bittorrent/session.h
  • src/base/bittorrent/sessionimpl.h
  • src/base/bittorrent/sessionimpl.cpp

Desktop GUI

  • src/gui/optionsdialog.cpp
  • src/gui/optionsdialog.ui

Web UI

  • src/webui/www/private/views/preferences.html

Web API

  • src/webui/api/appcontroller.cpp

API Changes

Web API Preferences Endpoints

  • slow_torrent_detection_enabled (boolean): Enable/disable the feature
  • slow_torrent_detection_duration (integer): Monitoring window in minutes
  • slow_torrent_minimum_progress (integer): Minimum progress in MiB
  • slow_torrent_excluded_tag (string): Tag name to exclude from detection (optional)

Backward Compatibility

  • All changes are fully backward compatible
  • Existing functionality remains unchanged when move slow downloading torrents to queue end is disabled
  • No breaking changes to existing API methods
  • New API methods extend the interface without modifying existing methods
  • Still hopefully, maintainers or contributors can step in and help out.

Perhaps my code wasn’t quite “maintainer material” 🌱—no worries! It’s been a solid month since the PR, so I’ve rolled my own little sh script (curl + jq, webapi-based) for personal use.

I trimmed a few bits for my own needs (no new features—the PR remains the “full edition”), but it’s easy to tweak if anyone’s interested. Both the PR and this script have clocked 100+ hours on Windows/Linux without a hiccup (that my humble skills could spot, anyway).

Script below

Run: /path/to/.sh &

#!/bin/sh

WEBUI_URL="http://localhost:000000"
USERNAME="000000"
PASSWORD="000000"
TARGET_TAG="000000"
SPEED_THRESHOLD=$((1*2*3*4))
CHECK_DURATION=$((1*2*3*4))
POLL_INTERVAL=10

samples=$(( (CHECK_DURATION + POLL_INTERVAL - 1) / POLL_INTERVAL ))
snapshot='{}'

do_login() {
  sid=$(curl -s -i -d "username=$USERNAME&password=$PASSWORD" "$WEBUI_URL/api/v2/auth/login" | grep -i '^set-cookie:' | sed 's/.*SID=\([^;]*\).*/SID=\1/')
  [ -n "$sid" ]
}

do_login || exit 1

http_get() {
  curl -s -b "$sid" "$1"
}

http_post() {
  status=$(curl -s -b "$sid" -d "$2" "$1" -w "%{http_code}" -o /dev/null)
}

while :; do
  resp=$(http_get "$WEBUI_URL/api/v2/torrents/info?filter=downloading")

  if [ -z "$resp" ] || printf '%s' "$resp" | grep -qi 'forbidden\|unauthorized\|login'; then
    do_login || { sleep $POLL_INTERVAL; continue; }
    resp=$(http_get "$WEBUI_URL/api/v2/torrents/info?filter=downloading")
  fi

  echo "$resp" | jq -e 'any(.[]; .state=="queuedDL")' >/dev/null || {
    snapshot='{}'
    sleep $POLL_INTERVAL
    continue
  }

  cur=$(echo "$resp" | jq --arg tag "$TARGET_TAG" '
    map(select(
      (.state == "downloading" or .state == "metaDL" or .state == "stalledDL") and
      (((.tags // "") | split(",") | map(ltrimstr(" ") | rtrimstr(" ")) | index($tag)) == null)
    )) | map({(.hash): (.downloaded // 0)}) | add
  ')
  [ "$cur" = "null" ] && { sleep $POLL_INTERVAL; continue; }

  snapshot=$(echo "$snapshot" | jq --argjson cur "$cur" --argjson samples "$samples" '
    with_entries(select(.key as $k | $cur | has($k)))
    | reduce ($cur | to_entries[]) as $item (.;
        .[$item.key] = ((.[$item.key] // []) + [$item.value])[-$samples:]
      )
  ')

  slow=$(echo "$snapshot" | jq -r --argjson samples "$samples" --argjson threshold "$SPEED_THRESHOLD" '
    [
      to_entries[]
      | select(
          (.value | length) == $samples and
          (.value[-1] - .value[0]) < $threshold
        )
      | .key
    ] | join("|")
  ')

  if [ -n "$slow" ]; then
    http_post "$WEBUI_URL/api/v2/torrents/bottomPrio" "hashes=$slow"

    if [ "$status" = "401" ] || [ "$status" = "403" ]; then
      do_login && http_post "$WEBUI_URL/api/v2/torrents/bottomPrio" "hashes=$slow"
    fi

    snapshot=$(echo "$snapshot" | jq --arg hashes "$slow" '
      reduce ($hashes | split("|")[]) as $h (.; del(.[$h]))
    ')
  fi

  sleep $POLL_INTERVAL
done

@github-actions
Copy link

This PR is stale because it has been 60 days with no activity. This PR will be automatically closed within 7 days if there is no further activity.

@github-actions github-actions bot added the Stale label Feb 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant