#!/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 [concurrency] # ./qbt-relink-daily-batch.sh jeopardy "/data/video/tv/Jeopardy! (1984)" 4 # # Runs up to 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/.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 [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/.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/.log for detail):" for h in "${fail_list[@]}"; do echo " $h" done fi