From 7c65401b2ce8758b6a48da4c1cb7cc1e6c638709 Mon Sep 17 00:00:00 2001 From: poprhythm Date: Wed, 9 Sep 2026 12:53:25 +0000 Subject: [PATCH] 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 --- qbt-relink-daily-batch.sh | 105 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100755 qbt-relink-daily-batch.sh diff --git a/qbt-relink-daily-batch.sh b/qbt-relink-daily-batch.sh new file mode 100755 index 0000000..0e083e9 --- /dev/null +++ b/qbt-relink-daily-batch.sh @@ -0,0 +1,105 @@ +#!/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