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
106 lines
3.1 KiB
Bash
Executable File
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
|