Poll MoveSeries command to completion instead of trusting DB fields

Confirmed live: DB fields flip instantly on the moveFiles=true PUT but the
physical move drains async, one command at a time — a batch could report
every series moved while most were still sitting at the old path.
This commit is contained in:
Gmer4Lfe
2026-07-17 22:54:44 -04:00
parent 1e1129687f
commit 4f0cd1083a
2 changed files with 55 additions and 4 deletions
+51 -4
View File
@@ -76,10 +76,13 @@
# rootFolderPath/path corrected and an immediate SeriesSearch triggered rather than a file # rootFolderPath/path corrected and an immediate SeriesSearch triggered rather than a file
# move (mirrors radarr_classification_scan.sh's handling of hasFile=false movies). # move (mirrors radarr_classification_scan.sh's handling of hasFile=false movies).
# #
# One series at a time, verified after each — a rapid-fire batch of Sonarr moves is exactly # One series at a time, verified after each. moveFiles=true flips the DB (rootFolderPath/
# what raced Sonarr's own file-move worker earlier this session doing this by hand (2 series # episodeFileCount) instantly, but the physical move is a separate async MoveSeries command
# reported "success" while their files sat at an intermediate path). A short sleep plus a # Sonarr drains one at a time internally — DB fields alone can report "moved" while the real
# real re-fetch-and-check after every single move catches that here before it can compound. # files are still sitting at the old path behind other queued moves (confirmed live: "Full
# House" reported episodeFileCount:192 at the new path via API while the actual 75GB/192
# files hadn't moved yet). Each move polls its own MoveSeries command to "completed" before
# the DB-field check runs, so a batch can't compound the race the way a bare sleep-and-check did.
# #
# ============================================================================================== # ==============================================================================================
# DESIGN PRINCIPLES # DESIGN PRINCIPLES
@@ -355,6 +358,50 @@ if [[ "$MOVE_MODE" == true ]]; then
break break
fi fi
# moveFiles=true flips rootFolderPath/episodeFileCount in the DB instantly, but the actual
# physical move is a separate async MoveSeries command that Sonarr drains one at a time
# internally — confirmed live: "Full House" showed episodeFileCount:192 at the new path via
# API while the real 75GB/192 files were still sitting at the old path, MoveSeries queued
# behind ~20 others. The DB-field check below cannot see that: poll the actual command to
# completion first, or a batch run can report every series "moved" while most are still
# mid-drain.
if [[ -n "$move_qs" ]]; then
move_cmd_id=""
for _ in 1 2 3 4 5; do
move_cmd_id=$(curl -sf --max-time 10 -H "X-Api-Key: $SONARR_API_KEY" \
"${SONARR_URL}/api/v3/command" 2>/dev/null | \
jq -r --argjson sid "$id" \
'[.[] | select(.name == "MoveSeries" and .body.seriesId == $sid)] | sort_by(.id) | last | .id // empty' \
2>/dev/null)
[[ -n "$move_cmd_id" ]] && break
sleep 1
done
if [[ -z "$move_cmd_id" ]]; then
error "$title — could not locate the MoveSeries command — stopping (review before re-running)"
(( FAILED++ ))
break
fi
info "$title: MoveSeries command $move_cmd_id queued, waiting for completion..."
move_status="" move_polled=0
while [[ "$move_polled" -lt "$SONARR_MOVE_POLL_TIMEOUT" ]]; do
move_status=$(curl -sf --max-time 10 -H "X-Api-Key: $SONARR_API_KEY" \
"${SONARR_URL}/api/v3/command/${move_cmd_id}" 2>/dev/null | \
jq -r '.status // empty' 2>/dev/null)
[[ "$move_status" == "completed" || "$move_status" == "failed" ]] && break
sleep 10
(( move_polled += 10 ))
[[ $(( move_polled % 60 )) -eq 0 ]] && log " still moving $title... (${move_polled}s elapsed)"
done
if [[ "$move_status" != "completed" ]]; then
error "$title — MoveSeries command $move_cmd_id ended as '${move_status:-timed out after ${SONARR_MOVE_POLL_TIMEOUT}s}' — stopping"
(( FAILED++ ))
break
fi
fi
sleep 3 sleep 3
# Never trust the PUT response alone — re-fetch and confirm the change actually landed. # Never trust the PUT response alone — re-fetch and confirm the change actually landed.
+4
View File
@@ -1180,6 +1180,10 @@
# protects against API returning partial data on a bad day # protects against API returning partial data on a bad day
SONARR_TRACKED_COUNT_FILE="$DATA_DIR/sonarr_tracked.count" SONARR_TRACKED_COUNT_FILE="$DATA_DIR/sonarr_tracked.count"
SONARR_IMPORT_SCAN_TIMEOUT=600 # seconds to wait for pre-flight import scan SONARR_IMPORT_SCAN_TIMEOUT=600 # seconds to wait for pre-flight import scan
SONARR_MOVE_POLL_TIMEOUT=3600 # seconds to wait for a single async MoveSeries command to
# reach "completed" — generous because a large series can sit
# queued behind other moves already in progress, not just its
# own copy time
SONARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "ts" "wmv" "mov") SONARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "ts" "wmv" "mov")
SONARR_PROTECTED_PATTERNS=( SONARR_PROTECTED_PATTERNS=(
# Subtitles # Subtitles