Consolidate cleanup scripts' double filesystem walk into one
lidarr_cleanup.sh/sonarr_cleanup.sh/radarr_cleanup.sh each walked their full media root twice per run: once to classify files and total the deletion size for the safety-threshold check, then again to actually delete. The size check needs to know the total before deleting anything, not before knowing what to delete -- the classification pass now records orphan/junk paths as it finds them, and the deletion pass just reads that list instead of re-walking and re-classifying the whole tree again. Only affects real (non-dry-run) executions, where the second walk used to happen. Also merges two separate stat calls per file into one.
This commit is contained in:
@@ -423,6 +423,14 @@ JUNK_BYTES=0
|
||||
AGE_SECONDS=$(( LIDARR_ORPHAN_AGE * 86400 ))
|
||||
NOW=$(date +%s)
|
||||
|
||||
# Files classified ORPHAN/JUNK below get their path recorded here, so the deletion pass can
|
||||
# just delete them directly instead of re-walking and re-classifying the whole tree a second
|
||||
# time (2026-07-17) — the size-threshold check below needs to know the total before deleting
|
||||
# anything, not before knowing what to delete, so there's no need to redo the classification
|
||||
# itself once it's already been decided.
|
||||
TO_DELETE_FILE="$TMP_DIR/to_delete_paths.txt"
|
||||
> "$TO_DELETE_FILE"
|
||||
|
||||
while IFS= read -r filepath; do
|
||||
[[ -z "$filepath" ]] && continue
|
||||
|
||||
@@ -439,10 +447,10 @@ while IFS= read -r filepath; do
|
||||
continue
|
||||
fi
|
||||
|
||||
FILE_SIZE=$(stat -c%s "$filepath" 2>/dev/null || echo 0)
|
||||
# Single stat call for both fields instead of two separate subprocess invocations.
|
||||
read -r FILE_SIZE FILE_MTIME < <(stat -c '%s %Y' "$filepath" 2>/dev/null || echo "0 0")
|
||||
|
||||
if has_extension "$filepath" "${LIDARR_EXTENSIONS[@]}"; then
|
||||
FILE_MTIME=$(stat -c %Y "$filepath" 2>/dev/null || echo 0)
|
||||
FILE_AGE=$(( NOW - FILE_MTIME ))
|
||||
|
||||
if [[ "$FILE_AGE" -lt "$AGE_SECONDS" ]] && [[ "$SKIP_AGE_CHECK" != true ]]; then
|
||||
@@ -454,10 +462,12 @@ while IFS= read -r filepath; do
|
||||
warn "$ICON_TRASH ORPHAN: $filepath"
|
||||
(( ORPHAN_COUNT++ ))
|
||||
ORPHAN_BYTES=$(( ORPHAN_BYTES + FILE_SIZE ))
|
||||
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||
else
|
||||
log "JUNK: $filepath"
|
||||
(( JUNK_COUNT++ ))
|
||||
JUNK_BYTES=$(( JUNK_BYTES + FILE_SIZE ))
|
||||
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||
fi
|
||||
|
||||
done < <(find "$LIDARR_MUSIC_ROOT" -type f 2>/dev/null)
|
||||
@@ -471,24 +481,13 @@ TOTAL_REMOVED=$(( ORPHAN_COUNT + JUNK_COUNT ))
|
||||
check_delete_size_threshold "$TOTAL_DELETE_BYTES" "$LIDARR_MAX_DELETE_GB" "Lidarr Cleanup"
|
||||
|
||||
# ── Execute Deletions ─────────────────────────────────────────────────────────────────────────
|
||||
# All safety layers passed — delete orphans and junk
|
||||
# All safety layers passed — delete orphans and junk. Reuses TO_DELETE_FILE from the
|
||||
# classification pass above instead of re-walking and re-classifying the whole tree again.
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
while IFS= read -r filepath; do
|
||||
[[ -z "$filepath" ]] && continue
|
||||
[[ -n "${TRACKED_MAP[$filepath]:-}" ]] && continue
|
||||
matches_pattern_list "$filepath" "${LIDARR_PROTECTED_PATTERNS[@]}" && continue
|
||||
|
||||
FILE_MTIME=$(stat -c %Y "$filepath" 2>/dev/null || echo 0)
|
||||
FILE_AGE=$(( NOW - FILE_MTIME ))
|
||||
|
||||
if has_extension "$filepath" "${LIDARR_EXTENSIONS[@]}"; then
|
||||
[[ "$FILE_AGE" -lt "$AGE_SECONDS" ]] && \
|
||||
[[ "$SKIP_AGE_CHECK" != true ]] && continue
|
||||
fi
|
||||
|
||||
rm -f "$filepath" 2>/dev/null || error "Failed to delete: $filepath"
|
||||
|
||||
done < <(find "$LIDARR_MUSIC_ROOT" -type f 2>/dev/null)
|
||||
done < "$TO_DELETE_FILE"
|
||||
|
||||
info "Cleaning up empty folders..."
|
||||
find "$LIDARR_MUSIC_ROOT" -mindepth 1 -type d -empty -delete 2>/dev/null
|
||||
|
||||
@@ -406,6 +406,13 @@ JUNK_BYTES=0
|
||||
AGE_SECONDS=$(( RADARR_ORPHAN_AGE * 86400 ))
|
||||
NOW=$(date +%s)
|
||||
|
||||
# Files classified ORPHAN/JUNK below get their path recorded here, so the deletion pass can
|
||||
# just delete them directly instead of re-walking and re-classifying every SCAN_ROOTS entry a
|
||||
# second time (2026-07-17) — the size-threshold check below needs to know the total before
|
||||
# deleting anything, not before knowing what to delete.
|
||||
TO_DELETE_FILE="$TMP_DIR/to_delete_paths.txt"
|
||||
> "$TO_DELETE_FILE"
|
||||
|
||||
while IFS= read -r filepath; do
|
||||
[[ -z "$filepath" ]] && continue
|
||||
|
||||
@@ -420,10 +427,10 @@ while IFS= read -r filepath; do
|
||||
continue
|
||||
fi
|
||||
|
||||
FILE_SIZE=$(stat -c%s "$filepath" 2>/dev/null || echo 0)
|
||||
# Single stat call for both fields instead of two separate subprocess invocations.
|
||||
read -r FILE_SIZE FILE_MTIME < <(stat -c '%s %Y' "$filepath" 2>/dev/null || echo "0 0")
|
||||
|
||||
if has_extension "$filepath" "${RADARR_EXTENSIONS[@]}"; then
|
||||
FILE_MTIME=$(stat -c %Y "$filepath" 2>/dev/null || echo 0)
|
||||
FILE_AGE=$(( NOW - FILE_MTIME ))
|
||||
|
||||
if [[ "$FILE_AGE" -lt "$AGE_SECONDS" ]] && [[ "$SKIP_AGE_CHECK" != true ]]; then
|
||||
@@ -435,10 +442,12 @@ while IFS= read -r filepath; do
|
||||
warn "$ICON_TRASH ORPHAN: $filepath"
|
||||
(( ORPHAN_COUNT++ ))
|
||||
ORPHAN_BYTES=$(( ORPHAN_BYTES + FILE_SIZE ))
|
||||
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||
else
|
||||
log "JUNK: $filepath"
|
||||
(( JUNK_COUNT++ ))
|
||||
JUNK_BYTES=$(( JUNK_BYTES + FILE_SIZE ))
|
||||
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||
fi
|
||||
|
||||
done < <(
|
||||
@@ -456,27 +465,13 @@ TOTAL_REMOVED=$(( ORPHAN_COUNT + JUNK_COUNT ))
|
||||
check_delete_size_threshold "$TOTAL_DELETE_BYTES" "$RADARR_MAX_DELETE_GB" "Radarr Cleanup"
|
||||
|
||||
# ── Execute Deletions ─────────────────────────────────────────────────────────────────────────
|
||||
# Reuses TO_DELETE_FILE from the classification pass above instead of re-walking and
|
||||
# re-classifying every SCAN_ROOTS entry again.
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
while IFS= read -r filepath; do
|
||||
[[ -z "$filepath" ]] && continue
|
||||
[[ -n "${TRACKED_MAP[$filepath]:-}" ]] && continue
|
||||
matches_pattern_list "$filepath" "${RADARR_PROTECTED_PATTERNS[@]}" && continue
|
||||
|
||||
FILE_MTIME=$(stat -c %Y "$filepath" 2>/dev/null || echo 0)
|
||||
FILE_AGE=$(( NOW - FILE_MTIME ))
|
||||
|
||||
if has_extension "$filepath" "${RADARR_EXTENSIONS[@]}"; then
|
||||
[[ "$FILE_AGE" -lt "$AGE_SECONDS" ]] && \
|
||||
[[ "$SKIP_AGE_CHECK" != true ]] && continue
|
||||
fi
|
||||
|
||||
rm -f "$filepath" 2>/dev/null || error "Failed to delete: $filepath"
|
||||
|
||||
done < <(
|
||||
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
||||
done | sort -u
|
||||
)
|
||||
done < "$TO_DELETE_FILE"
|
||||
|
||||
info "Cleaning up empty folders..."
|
||||
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||
|
||||
@@ -405,6 +405,13 @@ JUNK_BYTES=0
|
||||
AGE_SECONDS=$(( SONARR_ORPHAN_AGE * 86400 ))
|
||||
NOW=$(date +%s)
|
||||
|
||||
# Files classified ORPHAN/JUNK below get their path recorded here, so the deletion pass can
|
||||
# just delete them directly instead of re-walking and re-classifying every SCAN_ROOTS entry a
|
||||
# second time (2026-07-17) — the size-threshold check below needs to know the total before
|
||||
# deleting anything, not before knowing what to delete.
|
||||
TO_DELETE_FILE="$TMP_DIR/to_delete_paths.txt"
|
||||
> "$TO_DELETE_FILE"
|
||||
|
||||
while IFS= read -r filepath; do
|
||||
[[ -z "$filepath" ]] && continue
|
||||
|
||||
@@ -419,10 +426,10 @@ while IFS= read -r filepath; do
|
||||
continue
|
||||
fi
|
||||
|
||||
FILE_SIZE=$(stat -c%s "$filepath" 2>/dev/null || echo 0)
|
||||
# Single stat call for both fields instead of two separate subprocess invocations.
|
||||
read -r FILE_SIZE FILE_MTIME < <(stat -c '%s %Y' "$filepath" 2>/dev/null || echo "0 0")
|
||||
|
||||
if has_extension "$filepath" "${SONARR_EXTENSIONS[@]}"; then
|
||||
FILE_MTIME=$(stat -c %Y "$filepath" 2>/dev/null || echo 0)
|
||||
FILE_AGE=$(( NOW - FILE_MTIME ))
|
||||
|
||||
if [[ "$FILE_AGE" -lt "$AGE_SECONDS" ]] && [[ "$SKIP_AGE_CHECK" != true ]]; then
|
||||
@@ -434,10 +441,12 @@ while IFS= read -r filepath; do
|
||||
warn "$ICON_TRASH ORPHAN: $filepath"
|
||||
(( ORPHAN_COUNT++ ))
|
||||
ORPHAN_BYTES=$(( ORPHAN_BYTES + FILE_SIZE ))
|
||||
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||
else
|
||||
log "JUNK: $filepath"
|
||||
(( JUNK_COUNT++ ))
|
||||
JUNK_BYTES=$(( JUNK_BYTES + FILE_SIZE ))
|
||||
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||
fi
|
||||
|
||||
done < <(
|
||||
@@ -455,27 +464,13 @@ TOTAL_REMOVED=$(( ORPHAN_COUNT + JUNK_COUNT ))
|
||||
check_delete_size_threshold "$TOTAL_DELETE_BYTES" "$SONARR_MAX_DELETE_GB" "Sonarr Cleanup"
|
||||
|
||||
# ── Execute Deletions ─────────────────────────────────────────────────────────────────────────
|
||||
# Reuses TO_DELETE_FILE from the classification pass above instead of re-walking and
|
||||
# re-classifying every SCAN_ROOTS entry again.
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
while IFS= read -r filepath; do
|
||||
[[ -z "$filepath" ]] && continue
|
||||
[[ -n "${TRACKED_MAP[$filepath]:-}" ]] && continue
|
||||
matches_pattern_list "$filepath" "${SONARR_PROTECTED_PATTERNS[@]}" && continue
|
||||
|
||||
FILE_MTIME=$(stat -c %Y "$filepath" 2>/dev/null || echo 0)
|
||||
FILE_AGE=$(( NOW - FILE_MTIME ))
|
||||
|
||||
if has_extension "$filepath" "${SONARR_EXTENSIONS[@]}"; then
|
||||
[[ "$FILE_AGE" -lt "$AGE_SECONDS" ]] && \
|
||||
[[ "$SKIP_AGE_CHECK" != true ]] && continue
|
||||
fi
|
||||
|
||||
rm -f "$filepath" 2>/dev/null || error "Failed to delete: $filepath"
|
||||
|
||||
done < <(
|
||||
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
||||
done | sort -u
|
||||
)
|
||||
done < "$TO_DELETE_FILE"
|
||||
|
||||
info "Cleaning up empty folders..."
|
||||
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||
|
||||
Reference in New Issue
Block a user