Files
Musicbrainz-Picard/unRAID Userscript/music_sync.sh
2026-04-03 13:22:51 +02:00

233 lines
5.1 KiB
Bash

#!/bin/bash
# ----------------------------------------
# CONFIG
# ----------------------------------------
INCOMING="/mnt/user/Cache/Picard/incoming"
READY="/mnt/user/Cache/Picard/ready"
DEST1="/mnt/user/Cache/Syncthing/Julian/Musik/Thorsten -> Julian"
DEST2="/mnt/user/Media/Musik/[0] Navidrome"
LOG="/mnt/user/Cache/Picard/sync.log"
LOCKFILE="/mnt/user/Cache/Picard/sync.lock"
LOG_LINES=5000
DRY_RUN=false
CHOWN_USER="thorsten"
CHOWN_GROUP="users"
# ----------------------------------------
# NORMALIZE PATHS
# ----------------------------------------
normalize_path() {
local p="$1"
[[ "$p" != "/" ]] && p="${p%/}"
echo "$p"
}
INCOMING="$(normalize_path "$INCOMING")"
READY="$(normalize_path "$READY")"
DEST1="$(normalize_path "$DEST1")"
DEST2="$(normalize_path "$DEST2")"
# ----------------------------------------
# TRACKING
# ----------------------------------------
declare -A CREATED_DIRS_DEST1
declare -A CREATED_DIRS_DEST2
# ----------------------------------------
# FUNCTIONS
# ----------------------------------------
log() {
MSG="$(date '+%Y-%m-%d %H:%M:%S') | $1"
echo "$MSG"
echo "$MSG" >> "$LOG"
}
rotate_log() {
if [ -f "$LOG" ]; then
tail -n "$LOG_LINES" "$LOG" > "${LOG}.tmp" && mv "${LOG}.tmp" "$LOG"
fi
}
run_cmd() {
if [ "$DRY_RUN" = true ]; then
log "[DRY-RUN] $*"
else
"$@"
fi
}
rsync_cmd() {
if [ "$DRY_RUN" = true ]; then
rsync -a --dry-run "$@"
return 0
else
rsync -a "$@"
return $?
fi
}
# ----------------------------------------
# TRACK NEW DIRECTORIES
# ----------------------------------------
track_new_dirs() {
local BASE="$1"
local FULL="$2"
local PATH_ACCUM="$BASE"
IFS='/' read -ra PARTS <<< "${FULL#$BASE/}"
for PART in "${PARTS[@]}"; do
PATH_ACCUM="$PATH_ACCUM/$PART"
if [ ! -d "$PATH_ACCUM" ]; then
echo "$PATH_ACCUM"
fi
done
}
# ----------------------------------------
# VERIFY DESTINATIONS
# ----------------------------------------
for DEST in "$DEST1" "$DEST2"; do
if [ ! -d "$DEST" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') | ERROR: Destination not found -> $DEST"
exit 1
fi
done
# ----------------------------------------
# LOCKFILE
# ----------------------------------------
if [ -f "$LOCKFILE" ]; then
echo "Lockfile exists -> $LOCKFILE"
exit 1
fi
trap "rm -f '$LOCKFILE'" EXIT
touch "$LOCKFILE"
# ----------------------------------------
# QUICK EXIT
# ----------------------------------------
if ! find "$INCOMING" -type f -mmin +1 -print -quit | grep -q .; then
echo "$(date '+%Y-%m-%d %H:%M:%S') | No files to process -> exit"
exit 0
fi
# ----------------------------------------
# START
# ----------------------------------------
rotate_log
log "========================================"
log "START RUN (DRY_RUN=$DRY_RUN)"
log "========================================"
CHANGED=false
# ----------------------------------------
# 1. INCOMING → READY
# ----------------------------------------
log "---- MOVE: INCOMING → READY ----"
while read -r FILE; do
[ -f "$FILE" ] || continue
REL="${FILE#"$INCOMING"/}"
TARGET="$READY/$REL"
mkdir -p "$READY/$(dirname "$REL")"
log "MOVE -> $REL"
run_cmd mv "$FILE" "$TARGET"
done < <(find "$INCOMING" -type f -mmin +1)
# ----------------------------------------
# 2. READY → DESTS
# ----------------------------------------
log "---- SYNC: READY → DESTINATIONS ----"
log "DEST1: $DEST1"
log "DEST2: $DEST2"
while read -r FILE; do
[ -f "$FILE" ] || continue
REL="${FILE#"$READY"/}"
DEST1_FILE="$DEST1/$REL"
DEST2_FILE="$DEST2/$REL"
DIR1="$(dirname "$DEST1_FILE")"
DIR2="$(dirname "$DEST2_FILE")"
# track ALL new dirs
while read -r NEWDIR; do
CREATED_DIRS_DEST1["$NEWDIR"]=1
done < <(track_new_dirs "$DEST1" "$DIR1")
while read -r NEWDIR; do
CREATED_DIRS_DEST2["$NEWDIR"]=1
done < <(track_new_dirs "$DEST2" "$DIR2")
mkdir -p "$DIR1"
mkdir -p "$DIR2"
log "SYNC -> $REL"
rsync_cmd "$FILE" "$DEST1_FILE"
STATUS1=$?
rsync_cmd "$FILE" "$DEST2_FILE"
STATUS2=$?
if [[ $STATUS1 -eq 0 && $STATUS2 -eq 0 ]]; then
CHANGED=true
log "OK -> delete $REL"
run_cmd rm "$FILE"
else
log "ERROR -> $REL (dest1=$STATUS1 dest2=$STATUS2)"
fi
done < <(find "$READY" -type f 2>/dev/null)
# ----------------------------------------
# 3. FIX OWNERSHIP (PRECISION MODE)
# ----------------------------------------
log "CHANGED=$CHANGED"
if [ "$CHANGED" = true ]; then
log "FIX OWNERSHIP (state-aware)"
for DIR in "${!CREATED_DIRS_DEST1[@]}"; do
log "CHOWN DEST1 -> $DIR"
run_cmd chown -R "$CHOWN_USER:$CHOWN_GROUP" "$DIR"
done
for DIR in "${!CREATED_DIRS_DEST2[@]}"; do
log "CHOWN DEST2 -> $DIR"
run_cmd chown -R "$CHOWN_USER:$CHOWN_GROUP" "$DIR"
done
fi
# ----------------------------------------
# CLEANUP
# ----------------------------------------
if [ "$DRY_RUN" != true ]; then
find "$INCOMING" -mindepth 1 -type d -empty -delete
find "$READY" -mindepth 1 -type d -empty -delete 2>/dev/null
else
log "[DRY-RUN] Skipping cleanup"
fi
log "========================================"
log "END RUN"
log "========================================"