Files
Varaverk/Tools/arr_rescan_monitor.sh
T
Gmer4Lfe 2c3f0b9cb1 Guard arr cache writes against in-flight rescans, add rescan monitor tool
A direct arr_cache_write() call mid-rescan wrote a partial snapshot that
looked like real data loss to every consumer of the cache. The guard now
lives in arr_cache_write() itself so every caller is protected, not just
arr_get_tracked_data(). arr_rescan_monitor.sh closes the resulting gap for
rescans triggered outside arr_full_rescan.sh's own trigger-and-wait path.
2026-07-16 22:57:54 -04:00

98 lines
4.5 KiB
Bash
Executable File

#!/bin/bash
# ==============================================================================================
# ========================= Arr Rescan Monitor ==================================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Waits for an already-active rescan-type command (RescanFolders/RescanSeries/RescanMovie,
# DownloadedXScan, RefreshX — see ARR_RESCAN_COMMANDS in common.sh) on the given arr to finish,
# then refreshes the shared tracked-data cache (arr_cache_write) with the real post-scan
# numbers. Triggers nothing itself — for watching a rescan that was already started some
# other way (manual API call, arr's own UI, another script).
#
# Exists because arr_cache_write() now refuses to write while a rescan is active for that arr
# (2026-07-17 — a direct cache write mid-scan read as real data loss to every consumer of the
# cache, e.g. check_tracked_count_floor, instead of what it was: an in-flight scan). That
# guard protects the cache, but it also means nothing writes the real number through once the
# scan finishes unless something is watching for completion — arr_full_rescan.sh already does
# this for scans it triggers itself; this tool covers everything else.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# arr_rescan_monitor.sh lidarr
# Wait for lidarr's active rescan (if any) to finish, then refresh its cache.
# Exits immediately if no rescan is currently active — nothing to wait for.
#
# arr_rescan_monitor.sh lidarr 3600
# Override the default 2-hour wait ceiling (second positional arg, seconds).
#
# arr_rescan_monitor.sh lidarr --status
# Show whether a rescan is currently active and the cache's current age, no waiting.
#
# arr_rescan_monitor.sh lidarr --dry-run
# Show what would be waited on without actually waiting or writing the cache.
#
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../load_config.sh"
parse_args "$@"
detect_hosts
ARR_TYPE="${PARSED_ARGS[0]:-}"
TIMEOUT="${PARSED_ARGS[1]:-7200}"
case "$ARR_TYPE" in
lidarr|sonarr|radarr) ;;
*) error "Usage: arr_rescan_monitor.sh lidarr|sonarr|radarr [timeout_seconds] [--status] [--dry-run]"; exit 1 ;;
esac
url_var="${ARR_TYPE^^}_URL"; key_var="${ARR_TYPE^^}_API_KEY"
URL="${!url_var:-}"; KEY="${!key_var:-}"
VER="${ARR_API_VERSION[$ARR_TYPE]}"
require_var "$url_var"
require_var "$key_var"
ACTIVE=$(arr_active_rescan_command "$ARR_TYPE" "$URL" "$KEY" "$VER")
CACHE_AGE=$(arr_cache_age_seconds "$ARR_TYPE")
if [[ "$SHOW_STATUS" == true ]]; then
info "${ARR_TYPE^} active rescan: ${ACTIVE:-none}"
info "${ARR_TYPE^} cache age: $(( CACHE_AGE / 60 )) minutes"
exit 0
fi
if [[ -z "$ACTIVE" ]]; then
info "No active rescan on ${ARR_TYPE^} — nothing to wait for."
exit 0
fi
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would wait for ${ARR_TYPE^}'s active $ACTIVE (timeout ${TIMEOUT}s), then refresh its cache"
exit 0
fi
info "Waiting for ${ARR_TYPE^}'s active $ACTIVE to finish (timeout ${TIMEOUT}s)..."
if ! arr_wait_for_active_rescan "$ARR_TYPE" "$URL" "$KEY" "$VER" "$TIMEOUT"; then
error "Timed out after ${TIMEOUT}s waiting for ${ARR_TYPE^}'s rescan — cache not refreshed. Re-run once it finishes, or it'll pick up naturally next time something calls arr_get_tracked_data()."
exit 1
fi
ENDPOINT="${ARR_LIBRARY_ENDPOINT[$ARR_TYPE]}"
EXPR="${ARR_TRACKED_COUNT_EXPR[$ARR_TYPE]}"
ITEMS=$(curl -sf --max-time 60 -H "X-Api-Key: $KEY" "${URL}/api/${VER}/${ENDPOINT}" 2>/dev/null)
TOTAL=$(echo "$ITEMS" | jq "$EXPR" 2>/dev/null)
if [[ -z "$ITEMS" || -z "$TOTAL" || "$TOTAL" == "null" ]]; then
error "${ARR_TYPE^} rescan finished but couldn't fetch fresh library data — cache not refreshed"
exit 1
fi
arr_cache_write "$ARR_TYPE" "$ITEMS"
success "${ARR_TYPE^} rescan finished — cache refreshed, tracked: ${TOTAL}"
notify "${ARR_TYPE^} rescan finished on $(hostname) — cache refreshed, tracked: ${TOTAL}" "Arr Rescan Monitor" "normal"