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:
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user