Files
docker-infrastructure/qbt-relink-daily-batch.sh
T
poprhythm 7c65401b2c Add qbt-relink-daily-batch.sh to drive qbt-relink-daily.sh over
many single-file torrents

Runs a bounded-concurrency job pool (default 4) across every torrent
matching a name substring, logging each to its own file. The
orchestration here carries no data-safety risk beyond inefficiency
- each qbt-relink-daily.sh invocation is independently safe and
idempotent, so a driver bug can misreport or waste time but can't
corrupt data. Failures are logged and skipped, never auto-retried
within a run - re-running the whole batch picks up anything
incomplete since both scripts are idempotent.

Claude-Session: https://claude.ai/code/session_01HZQK6jHmdTpFjFZM8FUnqA
2026-09-09 12:53:25 +00:00

106 lines
3.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Batch-drive qbt-relink-daily.sh over every single-file torrent matching a
# name pattern (e.g. all ~1268 individual Jeopardy episode torrents).
#
# Usage:
# ./qbt-relink-daily-batch.sh <name-substring> <show-root-folder> [concurrency]
# ./qbt-relink-daily-batch.sh jeopardy "/data/video/tv/Jeopardy! (1984)" 4
#
# Runs up to <concurrency> qbt-relink-daily.sh invocations in parallel
# (different hashes only - the underlying script's own no-repeat-same-hash
# rule is unaffected since each torrent here is distinct). Each torrent's
# full log goes to /tmp/qbt-relink-daily-batch/<hash>.log. Failures are
# logged and skipped, never retried automatically within the same run -
# re-run the whole batch command afterward to pick up anything that failed
# or was skipped (both qbt-relink-daily.sh and this driver are idempotent:
# already-relinked torrents are detected and skipped instantly).
set -euo pipefail
ENV_FILE="$HOME/.claude-homelab/.env"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="/tmp/qbt-relink-daily-batch"
mkdir -p "$LOG_DIR"
if [[ ! -f "$ENV_FILE" ]]; then
echo "Error: $ENV_FILE not found" >&2
exit 1
fi
# shellcheck source=/dev/null
source "$ENV_FILE"
NAME_SUBSTRING="${1:-}"
SHOW_ROOT="${2:-}"
CONCURRENCY="${3:-4}"
if [[ -z "$NAME_SUBSTRING" || -z "$SHOW_ROOT" ]]; then
echo "Usage: $0 <name-substring> <show-root-folder> [concurrency]" >&2
exit 1
fi
COOKIE_JAR=$(mktemp)
trap 'rm -f "$COOKIE_JAR"' EXIT
curl -s -c "$COOKIE_JAR" -X POST "$QBITTORRENT_URL/api/v2/auth/login" \
--data-urlencode "username=$QBITTORRENT_USERNAME" \
--data-urlencode "password=$QBITTORRENT_PASSWORD" > /dev/null
hashes=$(curl -s -b "$COOKIE_JAR" "$QBITTORRENT_URL/api/v2/torrents/info" | python3 -c "
import json, sys
d = json.load(sys.stdin)
needle = sys.argv[1].lower()
for t in d:
if needle in t['name'].lower():
print(t['hash'])
" "$NAME_SUBSTRING")
total=$(echo "$hashes" | grep -c . || true)
echo "Found $total torrent(s) matching '$NAME_SUBSTRING'. Running with concurrency $CONCURRENCY."
echo "Per-torrent logs: $LOG_DIR/<hash>.log"
echo
ok=0
failed=0
skipped_running=0
declare -a fail_list=()
run_one() {
local hash="$1"
"$SCRIPT_DIR/qbt-relink-daily.sh" "$hash" "$SHOW_ROOT" > "$LOG_DIR/$hash.log" 2>&1
}
i=0
running=0
declare -A pids=()
for hash in $hashes; do
i=$((i + 1))
run_one "$hash" &
pids["$hash"]=$!
running=$((running + 1))
if [[ "$running" -ge "$CONCURRENCY" ]]; then
wait -n
running=$((running - 1))
fi
done
echo "All $i job(s) launched, waiting for the rest to finish..."
wait
echo
echo "=== Summary ==="
for hash in "${!pids[@]}"; do
if grep -q "No data loss detected" "$LOG_DIR/$hash.log" 2>/dev/null || grep -q "Nothing to do" "$LOG_DIR/$hash.log" 2>/dev/null; then
ok=$((ok + 1))
else
failed=$((failed + 1))
fail_list+=("$hash")
fi
done
echo "OK: $ok / $total"
echo "Failed or incomplete: $failed"
if [[ "$failed" -gt 0 ]]; then
echo "Failed hashes (see $LOG_DIR/<hash>.log for detail):"
for h in "${fail_list[@]}"; do
echo " $h"
done
fi