#!/usr/bin/env bash set -uo pipefail usage() { cat <<'EOF' Usage: ./batch.sh scan LIST.txt [--base '/source/base'] ./batch.sh recover LIST.txt [--base '/source/base'] Examples: ./batch.sh scan priority.txt ./batch.sh recover priority.txt ./batch.sh scan priority.txt --base '/Media' ./batch.sh recover priority.txt --base '/Media' EOF } if [[ $# -lt 2 || $# -gt 4 ]]; then usage >&2 exit 2 fi MODE="$1" LIST_FILE="$2" shift 2 BASE="" while [[ $# -gt 0 ]]; do case "$1" in --base) [[ $# -ge 2 ]] || { echo "ERROR: --base requires a path." >&2; exit 2; } BASE="$2" shift 2 ;; -h|--help) usage exit 0 ;; *) echo "ERROR: Unknown argument: $1" >&2 usage >&2 exit 2 ;; esac done case "$MODE" in scan|recover) ;; *) echo "ERROR: mode must be 'scan' or 'recover'." >&2; exit 2 ;; esac [[ -f "$LIST_FILE" ]] || { echo "ERROR: list file not found: $LIST_FILE" >&2; exit 1; } SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" WORKER="$SCRIPT_DIR/${MODE}.sh" [[ -x "$WORKER" ]] || { echo "ERROR: expected executable worker beside batch.sh: $WORKER" >&2 exit 1 } if [[ -n "$BASE" ]]; then [[ "$BASE" == /* ]] || BASE="/$BASE" BASE="${BASE%/}" fi if [[ -d /mnt/recovery && -w /mnt/recovery ]]; then LOG_DIR="/mnt/recovery" else LOG_DIR="$(cd -- "$(dirname -- "$LIST_FILE")" && pwd)" fi STAMP="$(date '+%Y%m%d-%H%M%S')" LIST_BASENAME="$(basename -- "$LIST_FILE")" LIST_STEM="${LIST_BASENAME%.*}" LOG_FILE="$LOG_DIR/batch-${MODE}-${LIST_STEM}-${STAMP}.log" SUMMARY_FILE="$LOG_DIR/batch-${MODE}-${LIST_STEM}-${STAMP}-summary.txt" declare -A SEEN=() total=0 ok=0 failed=0 ignored=0 { echo "Generic Plex Salvage Batch" echo "==========================" echo "Mode: $MODE" echo "List: $LIST_FILE" echo "Base: ${BASE:-}" echo "Started: $(date -Is)" echo } | tee -a "$LOG_FILE" while IFS= read -r raw || [[ -n "$raw" ]]; do line="${raw%$'\r'}" line="${line#"${line%%[![:space:]]*}"}" line="${line%"${line##*[![:space:]]}"}" if [[ -z "$line" || "$line" == \#* ]]; then ((ignored+=1)) continue fi if [[ "$line" == /* ]]; then source_path="$line" else if [[ -z "$BASE" ]]; then ((total+=1)) ((failed+=1)) echo "FAILED: relative entry requires --base: $line" | tee -a "$LOG_FILE" continue fi source_path="$BASE/$line" fi while [[ "$source_path" == *"//"* ]]; do source_path="${source_path//\/\//\/}" done if [[ -n "${SEEN[$source_path]+x}" ]]; then ((ignored+=1)) echo "SKIP duplicate: $source_path" | tee -a "$LOG_FILE" continue fi SEEN["$source_path"]=1 ((total+=1)) { echo "----------------------------------------------------------------" echo "[$total] ${MODE^^}: $source_path" echo "Started: $(date -Is)" } | tee -a "$LOG_FILE" set +e "$WORKER" "$source_path" 2>&1 | tee -a "$LOG_FILE" rc=${PIPESTATUS[0]} set -e if [[ $rc -eq 0 ]]; then ((ok+=1)) echo "RESULT: OK ($source_path)" | tee -a "$LOG_FILE" else ((failed+=1)) echo "RESULT: FAILED rc=$rc ($source_path)" | tee -a "$LOG_FILE" fi echo "Finished: $(date -Is)" | tee -a "$LOG_FILE" echo | tee -a "$LOG_FILE" done < "$LIST_FILE" { echo "GENERIC PLEX SALVAGE BATCH SUMMARY" echo "==================================" echo "Mode: $MODE" echo "List: $LIST_FILE" echo "Base: ${BASE:-}" echo "Completed: $(date -Is)" echo echo "Processed: $total" echo "Succeeded: $ok" echo "Failed: $failed" echo "Ignored: $ignored" echo echo "Full log: $LOG_FILE" } | tee "$SUMMARY_FILE" echo echo "Summary: $SUMMARY_FILE" [[ $failed -eq 0 ]]