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 ))
|
AGE_SECONDS=$(( LIDARR_ORPHAN_AGE * 86400 ))
|
||||||
NOW=$(date +%s)
|
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
|
while IFS= read -r filepath; do
|
||||||
[[ -z "$filepath" ]] && continue
|
[[ -z "$filepath" ]] && continue
|
||||||
|
|
||||||
@@ -439,10 +447,10 @@ while IFS= read -r filepath; do
|
|||||||
continue
|
continue
|
||||||
fi
|
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
|
if has_extension "$filepath" "${LIDARR_EXTENSIONS[@]}"; then
|
||||||
FILE_MTIME=$(stat -c %Y "$filepath" 2>/dev/null || echo 0)
|
|
||||||
FILE_AGE=$(( NOW - FILE_MTIME ))
|
FILE_AGE=$(( NOW - FILE_MTIME ))
|
||||||
|
|
||||||
if [[ "$FILE_AGE" -lt "$AGE_SECONDS" ]] && [[ "$SKIP_AGE_CHECK" != true ]]; then
|
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"
|
warn "$ICON_TRASH ORPHAN: $filepath"
|
||||||
(( ORPHAN_COUNT++ ))
|
(( ORPHAN_COUNT++ ))
|
||||||
ORPHAN_BYTES=$(( ORPHAN_BYTES + FILE_SIZE ))
|
ORPHAN_BYTES=$(( ORPHAN_BYTES + FILE_SIZE ))
|
||||||
|
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||||
else
|
else
|
||||||
log "JUNK: $filepath"
|
log "JUNK: $filepath"
|
||||||
(( JUNK_COUNT++ ))
|
(( JUNK_COUNT++ ))
|
||||||
JUNK_BYTES=$(( JUNK_BYTES + FILE_SIZE ))
|
JUNK_BYTES=$(( JUNK_BYTES + FILE_SIZE ))
|
||||||
|
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
done < <(find "$LIDARR_MUSIC_ROOT" -type f 2>/dev/null)
|
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"
|
check_delete_size_threshold "$TOTAL_DELETE_BYTES" "$LIDARR_MAX_DELETE_GB" "Lidarr Cleanup"
|
||||||
|
|
||||||
# ── Execute Deletions ─────────────────────────────────────────────────────────────────────────
|
# ── 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
|
if [[ "$DRY_RUN" == false ]]; then
|
||||||
while IFS= read -r filepath; do
|
while IFS= read -r filepath; do
|
||||||
[[ -z "$filepath" ]] && continue
|
[[ -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"
|
rm -f "$filepath" 2>/dev/null || error "Failed to delete: $filepath"
|
||||||
|
done < "$TO_DELETE_FILE"
|
||||||
done < <(find "$LIDARR_MUSIC_ROOT" -type f 2>/dev/null)
|
|
||||||
|
|
||||||
info "Cleaning up empty folders..."
|
info "Cleaning up empty folders..."
|
||||||
find "$LIDARR_MUSIC_ROOT" -mindepth 1 -type d -empty -delete 2>/dev/null
|
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 ))
|
AGE_SECONDS=$(( RADARR_ORPHAN_AGE * 86400 ))
|
||||||
NOW=$(date +%s)
|
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
|
while IFS= read -r filepath; do
|
||||||
[[ -z "$filepath" ]] && continue
|
[[ -z "$filepath" ]] && continue
|
||||||
|
|
||||||
@@ -420,10 +427,10 @@ while IFS= read -r filepath; do
|
|||||||
continue
|
continue
|
||||||
fi
|
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
|
if has_extension "$filepath" "${RADARR_EXTENSIONS[@]}"; then
|
||||||
FILE_MTIME=$(stat -c %Y "$filepath" 2>/dev/null || echo 0)
|
|
||||||
FILE_AGE=$(( NOW - FILE_MTIME ))
|
FILE_AGE=$(( NOW - FILE_MTIME ))
|
||||||
|
|
||||||
if [[ "$FILE_AGE" -lt "$AGE_SECONDS" ]] && [[ "$SKIP_AGE_CHECK" != true ]]; then
|
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"
|
warn "$ICON_TRASH ORPHAN: $filepath"
|
||||||
(( ORPHAN_COUNT++ ))
|
(( ORPHAN_COUNT++ ))
|
||||||
ORPHAN_BYTES=$(( ORPHAN_BYTES + FILE_SIZE ))
|
ORPHAN_BYTES=$(( ORPHAN_BYTES + FILE_SIZE ))
|
||||||
|
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||||
else
|
else
|
||||||
log "JUNK: $filepath"
|
log "JUNK: $filepath"
|
||||||
(( JUNK_COUNT++ ))
|
(( JUNK_COUNT++ ))
|
||||||
JUNK_BYTES=$(( JUNK_BYTES + FILE_SIZE ))
|
JUNK_BYTES=$(( JUNK_BYTES + FILE_SIZE ))
|
||||||
|
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
done < <(
|
done < <(
|
||||||
@@ -456,27 +465,13 @@ TOTAL_REMOVED=$(( ORPHAN_COUNT + JUNK_COUNT ))
|
|||||||
check_delete_size_threshold "$TOTAL_DELETE_BYTES" "$RADARR_MAX_DELETE_GB" "Radarr Cleanup"
|
check_delete_size_threshold "$TOTAL_DELETE_BYTES" "$RADARR_MAX_DELETE_GB" "Radarr Cleanup"
|
||||||
|
|
||||||
# ── Execute Deletions ─────────────────────────────────────────────────────────────────────────
|
# ── 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
|
if [[ "$DRY_RUN" == false ]]; then
|
||||||
while IFS= read -r filepath; do
|
while IFS= read -r filepath; do
|
||||||
[[ -z "$filepath" ]] && continue
|
[[ -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"
|
rm -f "$filepath" 2>/dev/null || error "Failed to delete: $filepath"
|
||||||
|
done < "$TO_DELETE_FILE"
|
||||||
done < <(
|
|
||||||
for host_path in "${SCAN_ROOTS[@]}"; do
|
|
||||||
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
|
||||||
done | sort -u
|
|
||||||
)
|
|
||||||
|
|
||||||
info "Cleaning up empty folders..."
|
info "Cleaning up empty folders..."
|
||||||
for host_path in "${SCAN_ROOTS[@]}"; do
|
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||||
|
|||||||
@@ -405,6 +405,13 @@ JUNK_BYTES=0
|
|||||||
AGE_SECONDS=$(( SONARR_ORPHAN_AGE * 86400 ))
|
AGE_SECONDS=$(( SONARR_ORPHAN_AGE * 86400 ))
|
||||||
NOW=$(date +%s)
|
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
|
while IFS= read -r filepath; do
|
||||||
[[ -z "$filepath" ]] && continue
|
[[ -z "$filepath" ]] && continue
|
||||||
|
|
||||||
@@ -419,10 +426,10 @@ while IFS= read -r filepath; do
|
|||||||
continue
|
continue
|
||||||
fi
|
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
|
if has_extension "$filepath" "${SONARR_EXTENSIONS[@]}"; then
|
||||||
FILE_MTIME=$(stat -c %Y "$filepath" 2>/dev/null || echo 0)
|
|
||||||
FILE_AGE=$(( NOW - FILE_MTIME ))
|
FILE_AGE=$(( NOW - FILE_MTIME ))
|
||||||
|
|
||||||
if [[ "$FILE_AGE" -lt "$AGE_SECONDS" ]] && [[ "$SKIP_AGE_CHECK" != true ]]; then
|
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"
|
warn "$ICON_TRASH ORPHAN: $filepath"
|
||||||
(( ORPHAN_COUNT++ ))
|
(( ORPHAN_COUNT++ ))
|
||||||
ORPHAN_BYTES=$(( ORPHAN_BYTES + FILE_SIZE ))
|
ORPHAN_BYTES=$(( ORPHAN_BYTES + FILE_SIZE ))
|
||||||
|
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||||
else
|
else
|
||||||
log "JUNK: $filepath"
|
log "JUNK: $filepath"
|
||||||
(( JUNK_COUNT++ ))
|
(( JUNK_COUNT++ ))
|
||||||
JUNK_BYTES=$(( JUNK_BYTES + FILE_SIZE ))
|
JUNK_BYTES=$(( JUNK_BYTES + FILE_SIZE ))
|
||||||
|
echo "$filepath" >> "$TO_DELETE_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
done < <(
|
done < <(
|
||||||
@@ -455,27 +464,13 @@ TOTAL_REMOVED=$(( ORPHAN_COUNT + JUNK_COUNT ))
|
|||||||
check_delete_size_threshold "$TOTAL_DELETE_BYTES" "$SONARR_MAX_DELETE_GB" "Sonarr Cleanup"
|
check_delete_size_threshold "$TOTAL_DELETE_BYTES" "$SONARR_MAX_DELETE_GB" "Sonarr Cleanup"
|
||||||
|
|
||||||
# ── Execute Deletions ─────────────────────────────────────────────────────────────────────────
|
# ── 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
|
if [[ "$DRY_RUN" == false ]]; then
|
||||||
while IFS= read -r filepath; do
|
while IFS= read -r filepath; do
|
||||||
[[ -z "$filepath" ]] && continue
|
[[ -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"
|
rm -f "$filepath" 2>/dev/null || error "Failed to delete: $filepath"
|
||||||
|
done < "$TO_DELETE_FILE"
|
||||||
done < <(
|
|
||||||
for host_path in "${SCAN_ROOTS[@]}"; do
|
|
||||||
[[ -d "$host_path" ]] && find "$host_path" -type f 2>/dev/null
|
|
||||||
done | sort -u
|
|
||||||
)
|
|
||||||
|
|
||||||
info "Cleaning up empty folders..."
|
info "Cleaning up empty folders..."
|
||||||
for host_path in "${SCAN_ROOTS[@]}"; do
|
for host_path in "${SCAN_ROOTS[@]}"; do
|
||||||
|
|||||||
Reference in New Issue
Block a user