#!/bin/bash # ----------------------------------------------------------------------------------------------- # --------------------------------- Transcode Cleanup ------------------------------------------ # ----------------------------------------------------------------------------------------------- # Removes old inactive transcode files from both ramdisk and SSD fallback locations. # Never deletes files belonging to active sessions or files being actively written. # After cleanup checks if ramdisk usage dropped enough to flip symlink back to ramdisk. # # Safety rules — a file is eligible for deletion only if ALL conditions are true: # 1. Older than TRANSCODE_MAX_AGE minutes (mtime) # 2. Not currently open by any process (lsof check) # 3. Not in an orphan grace period shorter than TRANSCODE_ORPHAN_AGE minutes # # Run every 5 minutes via cron/User Scripts plugin. # All configuration in Master.conf under Transcode Manager section. # Supports --dry-run to preview what would be deleted without making changes. # ----------------------------------------------------------------------------------------------- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../Master.conf" source "$SCRIPT_DIR/../common.sh" parse_args "$@" STATE_FILE="/tmp/transcode_state.db" # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_GEAR Setup ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━ $ICON_GEAR Setup ━━━" if [[ "$EUID" -ne 0 ]]; then error "Must be run as root" exit 1 fi success "Running as root" if ! command -v lsof >/dev/null 2>&1; then warn "lsof not available — active file check will be skipped" LSOF_AVAILABLE=false else LSOF_AVAILABLE=true fi # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_SUMMARY Status ━━━ # ----------------------------------------------------------------------------------------------- if [[ "$SHOW_STATUS" == true ]]; then echo "" echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━" echo "$ICON_RAM Ramdisk: $RAMDISK_PATH" echo "$ICON_DISK SSD fallback: $TRANSCODE_SSD" echo "$ICON_TRASH Max age: ${TRANSCODE_MAX_AGE} minutes" echo "$ICON_TRASH Orphan age: ${TRANSCODE_ORPHAN_AGE} minutes" echo "$ICON_GEAR lsof check: $LSOF_AVAILABLE" echo "$ICON_GEAR Dry Run: $DRY_RUN" echo "━━━━━━━━━━━━━━━━━━━━━━━" exit 0 fi [[ "$DRY_RUN" == true ]] && warn "DRY RUN — no files will be deleted" # ----------------------------------------------------------------------------------------------- # FILE SAFETY CHECK # Returns 0 (safe to delete) if file is old enough and not actively open. # Returns 1 (keep) if file should be preserved. # ----------------------------------------------------------------------------------------------- is_safe_to_delete() { local file="$1" max_age="$2" # Check age using find — if find returns the file it's old enough if ! find "$file" -maxdepth 0 -mmin +"$max_age" 2>/dev/null | grep -q .; then return 1 # Too recent fi # Check if file is actively open by any process if [[ "$LSOF_AVAILABLE" == true ]]; then if lsof "$file" >/dev/null 2>&1; then return 1 # File is open — being written or read fi fi return 0 # Safe to delete } # ----------------------------------------------------------------------------------------------- # CLEANUP FUNCTION # Scans a directory and removes eligible files. # Returns bytes freed. # ----------------------------------------------------------------------------------------------- cleanup_location() { local location="$1" label="$2" max_age="$3" local files_removed=0 local bytes_freed=0 local files_skipped=0 if [[ ! -d "$location" ]]; then warn "$label does not exist — skipping" return fi local file_count file_count=$(find "$location" -type f 2>/dev/null | wc -l) info "$ICON_TRASH $label: $file_count files to scan (age threshold: ${max_age}min)" while IFS= read -r file; do [[ -z "$file" ]] && continue if is_safe_to_delete "$file" "$max_age"; then local file_size file_size=$(stat -c%s "$file" 2>/dev/null || echo 0) if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would delete: $file" ((files_skipped++)) else if rm -f "$file" 2>/dev/null; then ((files_removed++)) bytes_freed=$((bytes_freed + file_size)) log "Deleted: $file" else warn "Could not delete: $file" ((files_skipped++)) fi fi else ((files_skipped++)) log "Keeping: $file (active or too recent)" fi done < <(find "$location" -type f 2>/dev/null) # Remove empty directories left behind if [[ "$DRY_RUN" == false ]]; then find "$location" -mindepth 1 -type d -empty -delete 2>/dev/null fi local freed_human if (( bytes_freed > 1073741824 )); then freed_human=$(awk "BEGIN {printf \"%.1fGB\", $bytes_freed / 1073741824}") elif (( bytes_freed > 1048576 )); then freed_human=$(awk "BEGIN {printf \"%.1fMB\", $bytes_freed / 1048576}") else freed_human="${bytes_freed}B" fi if [[ "$DRY_RUN" == true ]]; then info "$label — dry run complete ($files_skipped files scanned)" else success "$label — removed $files_removed files, freed $freed_human, skipped $files_skipped" fi # Export for summary LOCATION_REMOVED=$files_removed LOCATION_FREED=$freed_human LOCATION_SKIPPED=$files_skipped } # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_TRASH Transcode Cleanup ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━ $ICON_TRASH Transcode Cleanup — $(date '+%Y-%m-%d %H:%M:%S') ━━━" echo "" START=$(date +%s) TOTAL_REMOVED=0 TOTAL_SKIPPED=0 # Cleanup ramdisk if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then echo "━━━ $ICON_RAM Ramdisk ━━━" RAMDISK_BEFORE=$(df "$RAMDISK_PATH" | awk 'NR==2 {print $3}') cleanup_location "$RAMDISK_PATH" "Ramdisk" "$TRANSCODE_MAX_AGE" TOTAL_REMOVED=$((TOTAL_REMOVED + LOCATION_REMOVED)) TOTAL_SKIPPED=$((TOTAL_SKIPPED + LOCATION_SKIPPED)) RAMDISK_FREED=$LOCATION_FREED echo "" else warn "$ICON_RAM Ramdisk not mounted — skipping ramdisk cleanup" RAMDISK_FREED="0B" fi # Cleanup SSD fallback if [[ -d "$TRANSCODE_SSD" ]]; then echo "━━━ $ICON_DISK SSD Fallback ━━━" cleanup_location "$TRANSCODE_SSD" "SSD fallback" "$TRANSCODE_MAX_AGE" TOTAL_REMOVED=$((TOTAL_REMOVED + LOCATION_REMOVED)) TOTAL_SKIPPED=$((TOTAL_SKIPPED + LOCATION_SKIPPED)) SSD_FREED=$LOCATION_FREED echo "" else info "$ICON_DISK SSD fallback not found — skipping" SSD_FREED="0B" fi # ----------------------------------------------------------------------------------------------- # Post-cleanup symlink check # If ramdisk had space freed, check if manager should flip symlink back # ----------------------------------------------------------------------------------------------- if [[ "$DRY_RUN" == false ]] && mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then RAMDISK_USED_KB=$(df "$RAMDISK_PATH" | awk 'NR==2 {print $3}') LOW_THRESHOLD=$(awk "BEGIN {print $RAMDISK_LOW_GB * 1024 * 1024}") if (( RAMDISK_USED_KB < LOW_THRESHOLD )); then CURRENT_TARGET=$(grep "^current_target=" "$STATE_FILE" 2>/dev/null | cut -d'=' -f2) if [[ "$CURRENT_TARGET" == "$TRANSCODE_SSD" ]]; then info "$ICON_RAM Ramdisk has space after cleanup — triggering manager to flip back" bash "$SCRIPT_DIR/transcode_manager.sh" --no-log fi fi fi END=$(date +%s) # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_SUMMARY Summary ━━━ # ----------------------------------------------------------------------------------------------- echo "━━━━━ $ICON_SUMMARY TRANSCODE CLEANUP SUMMARY ━━━━━" echo "$ICON_RAM Ramdisk freed: $RAMDISK_FREED" echo "$ICON_DISK SSD freed: $SSD_FREED" echo "$ICON_TRASH Removed: $TOTAL_REMOVED files" echo "$ICON_TRASH Skipped: $TOTAL_SKIPPED files (active or recent)" echo "$ICON_TIME Duration: $(format_duration $((END - START)))" if [[ "$DRY_RUN" == true ]]; then echo "$ICON_WARN Status: DRY RUN — no files deleted" else echo "$ICON_DONE Status: $ICON_SUCCESS DONE" if [[ "$TOTAL_REMOVED" -gt 0 ]]; then notify "Transcode cleanup on $(hostname) — removed $TOTAL_REMOVED files (RAM: $RAMDISK_FREED SSD: $SSD_FREED)" "Transcode Cleanup" "normal" fi fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"