Poll MoveMovie command to completion instead of trusting DB fields
Mirrors the Sonarr fix — same MoveMovieService one-at-a-time drain architecture, never confirmed live on the Radarr side but the DB-instant/move-async split is identical, so the same batch-verification race applies.
This commit is contained in:
@@ -343,11 +343,14 @@ fi
|
|||||||
# like Castlevania/Legend of Korra legitimately live in the anime root without matching the
|
# like Castlevania/Legend of Korra legitimately live in the anime root without matching the
|
||||||
# anime signal — or on JUNK (those need removal from Radarr, not a file move).
|
# anime signal — or on JUNK (those need removal from Radarr, not a file move).
|
||||||
#
|
#
|
||||||
# One movie at a time, verified after each, matching the lesson learned doing this by hand
|
# One movie at a time, verified after each. moveFiles=true flips the DB (rootFolderPath/
|
||||||
# for Sonarr earlier: a rapid-fire batch of moves raced Radarr's own file-move worker and
|
# hasFile) instantly, but the physical move is a separate async MoveMovie command Radarr's
|
||||||
# left two series' files at an intermediate path while the API still reported success. A
|
# own MoveMovieService drains one at a time internally — same architecture that raced on the
|
||||||
# short sleep plus a real re-fetch-and-check after every single move catches that here
|
# Sonarr side (episodeFileCount reported at the new path via API while the real files were
|
||||||
# before it can compound across dozens of movies.
|
# still sitting at the old one, MoveSeries command queued behind ~20 others). Never confirmed
|
||||||
|
# live on the Radarr side, but the same DB-write-is-instant/move-is-async split applies, so
|
||||||
|
# each move here polls its own MoveMovie command to "completed" before the DB-field check
|
||||||
|
# runs, mirroring the Sonarr fix.
|
||||||
if [[ "$MOVE_MODE" == true ]]; then
|
if [[ "$MOVE_MODE" == true ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "━━━ $ICON_SYNC Move Mode — Forward Misplacements ━━━"
|
echo "━━━ $ICON_SYNC Move Mode — Forward Misplacements ━━━"
|
||||||
@@ -451,6 +454,48 @@ if [[ "$MOVE_MODE" == true ]]; then
|
|||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# moveFiles=true flips rootFolderPath/hasFile in the DB instantly, but the actual
|
||||||
|
# physical move is a separate async MoveMovie command that Radarr's MoveMovieService
|
||||||
|
# drains one at a time internally — mirrors the confirmed Sonarr race (see header
|
||||||
|
# comment above Move Mode). Poll the actual command to completion before trusting the
|
||||||
|
# DB-field check below.
|
||||||
|
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: $RADARR_API_KEY" \
|
||||||
|
"${RADARR_URL}/api/v3/command" 2>/dev/null | \
|
||||||
|
jq -r --argjson mid "$id" \
|
||||||
|
'[.[] | select(.name == "MoveMovie" and .body.movieId == $mid)] | 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 MoveMovie command — stopping (review before re-running)"
|
||||||
|
(( FAILED++ ))
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
info " → $title: MoveMovie command $move_cmd_id queued, waiting for completion..."
|
||||||
|
move_status="" move_polled=0
|
||||||
|
while [[ "$move_polled" -lt "$RADARR_MOVE_POLL_TIMEOUT" ]]; do
|
||||||
|
move_status=$(curl -sf --max-time 10 -H "X-Api-Key: $RADARR_API_KEY" \
|
||||||
|
"${RADARR_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 — MoveMovie command $move_cmd_id ended as '${move_status:-timed out after ${RADARR_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.
|
||||||
|
|||||||
@@ -1209,6 +1209,8 @@
|
|||||||
# protects against API returning partial data on a bad day
|
# protects against API returning partial data on a bad day
|
||||||
RADARR_TRACKED_COUNT_FILE="$DATA_DIR/radarr_tracked.count"
|
RADARR_TRACKED_COUNT_FILE="$DATA_DIR/radarr_tracked.count"
|
||||||
RADARR_IMPORT_SCAN_TIMEOUT=600 # seconds to wait for pre-flight import scan
|
RADARR_IMPORT_SCAN_TIMEOUT=600 # seconds to wait for pre-flight import scan
|
||||||
|
RADARR_MOVE_POLL_TIMEOUT=3600 # seconds to wait for a single async MoveMovie command to
|
||||||
|
# reach "completed" — mirrors SONARR_MOVE_POLL_TIMEOUT
|
||||||
RADARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "wmv" "mov")
|
RADARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "wmv" "mov")
|
||||||
RADARR_PROTECTED_PATTERNS=(
|
RADARR_PROTECTED_PATTERNS=(
|
||||||
# Subtitles
|
# Subtitles
|
||||||
|
|||||||
Reference in New Issue
Block a user