#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=darktable_common.sh source "$SCRIPT_DIR/darktable_common.sh" log_step "Darktable Sync gestartet (PID $$, Argumente: ${*:-keine})" check_dependency unison check_dependency ssh openssh-client check_dependency notify-send libnotify-bin check_dependency darktable log "Alle Abhängigkeiten vorhanden." load_config validate_config log "Konfiguration geladen: Server=$SERVER_USER@$SERVER_IP:$SERVER_SSH_PORT" log " DB lokal: $LOCAL_DARKTABLE_DB_DIR" log " DB Server: $SERVER_DB_DIR" log " Fotos lokal: $LOCAL_PHOTO_DIR" log " Fotos Server:$SERVER_PHOTO_DIR" export DISPLAY="${DISPLAY:-:0}" DRY_RUN=true SHOW_NOTIFY_START_STOP=false for arg in "$@"; do case "$arg" in --execute|-e) DRY_RUN=false ;; --with-notify-start-stop) SHOW_NOTIFY_START_STOP=true ;; esac done LOCKDIR="$CONFIG_DIR/sync.lock" LOCKPID="$LOCKDIR/pid" TMPFILES=() log "Lock anfordern: $LOCKDIR" if ! mkdir "$LOCKDIR" 2>/dev/null; then EXISTING_PID=$(cat "$LOCKPID" 2>/dev/null || true) if [ -n "$EXISTING_PID" ] && ! kill -0 "$EXISTING_PID" 2>/dev/null; then log "Verwaisten Lock gefunden (PID $EXISTING_PID läuft nicht mehr) – wird entfernt." rm -f "$LOCKPID" rmdir "$LOCKDIR" 2>/dev/null || true mkdir "$LOCKDIR" else log_error "Sync läuft bereits (PID ${EXISTING_PID:-unbekannt}). Lock: $LOCKDIR" echo " rmdir $LOCKDIR" >&2 exit 1 fi fi echo "$$" > "$LOCKPID" log "Lock erworben (PID $$)." trap 'rm -f "${TMPFILES[@]}" "$LOCKPID"; rmdir "$LOCKDIR" 2>/dev/null || true; log "Lock freigegeben."' EXIT if [ "$DRY_RUN" = true ]; then log_step "TROCKENLAUF – keine Änderungen werden vorgenommen" log "Dieser Aufruf zeigt nur, was synchronisiert werden würde." log "Für echten Sync: $(basename "$0") --execute oder -e" log "" if ! confirm_dry_run; then log "Trockenlauf abgebrochen." exit 0 fi fi log "Prüfen ob Darktable läuft..." if pgrep -x darktable > /dev/null 2>&1; then log "Darktable läuft (PID: $(pgrep -x darktable | tr '\n' ' ')) – Sync übersprungen." notify-send "Darktable Sync – Abbruch" \ "Darktable ist gerade geöffnet. Sync erst nach dem Beenden möglich." \ -u normal -t 8000 exit 0 fi log "Darktable läuft nicht – Sync kann fortfahren." if [ -f "$CONFIG_DIR/sync_pending" ]; then log "Ausstehender Sync aus vorherigem Lauf wird jetzt nachgeholt." notify-send "Darktable Sync" "Ausstehender Sync wird jetzt ausgeführt..." -t 3000 fi log "Serververbindung prüfen ($SERVER_USER@$SERVER_IP Port $SERVER_SSH_PORT)..." if ! server_reachable; then log "Server nicht erreichbar – Sync übersprungen, sync_pending gesetzt." touch "$CONFIG_DIR/sync_pending" exit 0 fi log "Server erreichbar." if [ "$SHOW_NOTIFY_START_STOP" = true ]; then notify-send "Darktable Sync" "Sync gestartet..." -t 3000 fi log "Unison-Versionen prüfen..." check_unison_versions log "Unison-Versionen übereinstimmend." log "Active-Marker auf Server prüfen..." ACTIVE=$(ssh_server "cat '$SERVER_DB_DIR/darktable.active' 2>/dev/null || true") if [ -n "$ACTIVE" ]; then log "WARNUNG: Active-Marker vorhanden: $ACTIVE" notify-send "Darktable Sync – Warnung" \ "Darktable läuft möglicherweise auf: $ACTIVE" -u normal -t 10000 else log "Kein Active-Marker – kein anderer Client aktiv." fi log "Darktable-Versionen prüfen..." SERVER_VERSION=$(ssh_server "cat '$SERVER_DB_DIR/darktable_version' 2>/dev/null || true") LOCAL_VERSION=$(darktable --version 2>&1 | head -1 || true) log " Lokal: ${LOCAL_VERSION:-unbekannt}" log " Server: ${SERVER_VERSION:-noch nicht gespeichert}" if [ -n "$SERVER_VERSION" ]; then LOCAL_MM=$(echo "$LOCAL_VERSION" | grep -oP '\d+\.\d+' | head -1 || true) SERVER_MM=$(echo "$SERVER_VERSION" | grep -oP '\d+\.\d+' | head -1 || true) if [ -n "$SERVER_MM" ] && [ "$LOCAL_MM" != "$SERVER_MM" ]; then log_error "Versionskonflikt: lokal=$LOCAL_MM, server=$SERVER_MM" log_error "Bitte beide Rechner auf gleichen Stand bringen." notify-send "Darktable Sync – Versionskonflikt" \ "Lokal: $LOCAL_MM Server: $SERVER_MM\nBitte angleichen!" \ -u critical touch "$CONFIG_DIR/sync_pending" exit 1 fi log "Versionen übereinstimmend ($LOCAL_MM)." fi log "Sync-Token prüfen..." SAVED_TOKEN=$(read_sync_token) SERVER_TOKEN=$(server_db_mtime) log " Gespeicherter Token: ${SAVED_TOKEN:-keiner (erster Sync)}" log " Aktueller Server-Token: $SERVER_TOKEN" UNISON_DB_FLAGS=(-prefer newer) UNISON_PHOTO_FLAGS=(-prefer newer) if [ -n "$SAVED_TOKEN" ] && [ "$SAVED_TOKEN" != "$SERVER_TOKEN" ]; then log "WARNUNG: Token-Konflikt (gespeichert=$SAVED_TOKEN, server=$SERVER_TOKEN) – Benutzer wird gefragt." RESOLUTION=$(ask_conflict_resolution) log "Benutzerentscheidung: $RESOLUTION" case "$RESOLUTION" in upload) log "Upload erzwungen – lokale Version überschreibt Server." UNISON_DB_FLAGS=(-force local) ;; abort) log "Sync abgebrochen durch Benutzer." exit 0 ;; *) log "Download – Server-Stand wird übernommen." UNISON_DB_FLAGS=(-force remote) UNISON_PHOTO_FLAGS=(-force remote) ;; esac else log "Token stimmt überein – bidirektionaler Sync mit neuerer Version bevorzugt." fi if [ "$DRY_RUN" = false ]; then log_step "Datenbank-Backup" log " $LOCAL_DARKTABLE_DB_DIR/library.db → library.db.bak" cp "$LOCAL_DARKTABLE_DB_DIR/library.db" "$LOCAL_DARKTABLE_DB_DIR/library.db.bak" log " $LOCAL_DARKTABLE_DB_DIR/data.db → data.db.bak" cp "$LOCAL_DARKTABLE_DB_DIR/data.db" "$LOCAL_DARKTABLE_DB_DIR/data.db.bak" log "Backup abgeschlossen." fi SYNC_LOG=$(mktemp) TMPFILES+=("$SYNC_LOG") UNISON_LOG_DB=$(mktemp) TMPFILES+=("$UNISON_LOG_DB") UNISON_LOG_PHOTOS=$(mktemp) TMPFILES+=("$UNISON_LOG_PHOTOS") BACKUP_PHOTO_DIR="${LOCAL_PHOTO_DIR}-bak" BACKUP_DB_DIR="${LOCAL_DARKTABLE_DB_DIR}-bak" if [ "$DRY_RUN" = false ]; then mkdir -p "$BACKUP_PHOTO_DIR" "$BACKUP_DB_DIR" fi UNISON_DRY_FLAG=() [ "$DRY_RUN" = true ] && UNISON_DRY_FLAG=(-dryrun) [ "$DRY_RUN" = true ] && DRY_SUFFIX=" (Trockenlauf)" || DRY_SUFFIX="" if [ -z "$(ls -A "$LOCAL_DARKTABLE_DB_DIR" 2>/dev/null)" ]; then log_error "Sync abgebrochen: Quellverzeichnis leer ($LOCAL_DARKTABLE_DB_DIR). Falscher Mount?" touch "$CONFIG_DIR/sync_pending" exit 1 fi if [ -z "$(ls -A "$LOCAL_PHOTO_DIR" 2>/dev/null)" ]; then log_error "Sync abgebrochen: Quellverzeichnis leer ($LOCAL_PHOTO_DIR). Falscher Mount?" touch "$CONFIG_DIR/sync_pending" exit 1 fi log_step "Sync: Datenbank${DRY_SUFFIX}" log " Lokal: $LOCAL_DARKTABLE_DB_DIR/" log " Server: $SERVER_USER@$SERVER_IP:$SERVER_DB_DIR/" if ! unison "$LOCAL_DARKTABLE_DB_DIR" \ "ssh://$SERVER_USER@$SERVER_IP/$SERVER_DB_DIR" \ -batch -times -auto \ "${UNISON_DB_FLAGS[@]}" \ "${UNISON_DRY_FLAG[@]}" \ -sshargs "-p $SERVER_SSH_PORT" \ -ignore "Name *.lock" \ -ignore "Name darktable_version" \ -backup "Name *" \ -backupdir "$BACKUP_DB_DIR" \ 2>&1 | tee -a "$SYNC_LOG" "$UNISON_LOG_DB"; then log_error "Sync Datenbank fehlgeschlagen ($LOCAL_DARKTABLE_DB_DIR)" touch "$CONFIG_DIR/sync_pending" exit 1 fi CHANGED_DB=$(count_unison_transferred "$UNISON_LOG_DB") log "Datenbank-Sync abgeschlossen: $CHANGED_DB Datei(en) geändert." log_step "Sync: Fotos${DRY_SUFFIX}" log " Lokal: $LOCAL_PHOTO_DIR/" log " Server: $SERVER_USER@$SERVER_IP:$SERVER_PHOTO_DIR/" if ! unison "$LOCAL_PHOTO_DIR" \ "ssh://$SERVER_USER@$SERVER_IP/$SERVER_PHOTO_DIR" \ -batch -times -auto \ "${UNISON_PHOTO_FLAGS[@]}" \ "${UNISON_DRY_FLAG[@]}" \ -sshargs "-p $SERVER_SSH_PORT" \ -ignore "Name *.lock" \ -backup "Name *" \ -backupdir "$BACKUP_PHOTO_DIR" \ 2>&1 | tee -a "$SYNC_LOG" "$UNISON_LOG_PHOTOS"; then log_error "Sync Fotos fehlgeschlagen ($LOCAL_PHOTO_DIR)" touch "$CONFIG_DIR/sync_pending" exit 1 fi CHANGED_PHOTOS=$(count_unison_transferred "$UNISON_LOG_PHOTOS") log "Foto-Sync abgeschlossen: $CHANGED_PHOTOS Datei(en) geändert." if [ "$DRY_RUN" = false ]; then NEW_TOKEN=$(server_db_mtime) save_sync_token "$NEW_TOKEN" log "Sync-Token gespeichert: $NEW_TOKEN" log "Versionsdatei aktualisieren: $LOCAL_DARKTABLE_DB_DIR/darktable_version" echo "$LOCAL_VERSION" > "$LOCAL_DARKTABLE_DB_DIR/darktable_version" rm -f "$CONFIG_DIR/sync_pending" log "sync_pending entfernt." log_step "Backup-Bereinigung (älter als 2 Jahre)" cleanup_old_backups "$BACKUP_PHOTO_DIR" cleanup_old_backups "$BACKUP_DB_DIR" fi TOTAL_CHANGED=$((CHANGED_DB + CHANGED_PHOTOS)) if [ "$DRY_RUN" = true ]; then all_log=$(cat "$UNISON_LOG_DB" "$UNISON_LOG_PHOTOS" 2>/dev/null || true) UP_NEW=$( echo "$all_log" | grep -cE 'new file.*---->' || true) UP_UPD=$( echo "$all_log" | grep -cE 'changed.*---->' || true) UP_DEL=$( echo "$all_log" | grep -cE 'deleted.*---->' || true) DN_NEW=$( echo "$all_log" | grep -cE '<----.*new file' || true) DN_UPD=$( echo "$all_log" | grep -cE '<----.*changed' || true) DN_DEL=$( echo "$all_log" | grep -cE '<----.*deleted' || true) log_step "Trockenlauf-Ergebnis" log " Upload: $UP_NEW neu | $UP_UPD aktualisiert | $UP_DEL gelöscht" log " Download: $DN_NEW neu | $DN_UPD aktualisiert | $DN_DEL gelöscht → Backup: $BACKUP_PHOTO_DIR" if [ "$((UP_NEW + UP_UPD + UP_DEL + DN_NEW + DN_UPD + DN_DEL))" -gt 0 ]; then if ask_user "Details" "Details der zu übertragenden Dateien anzeigen?"; then format_unison_details "$UNISON_LOG_DB" "Upload DB" "up" format_unison_details "$UNISON_LOG_DB" "Download DB" "down" format_unison_details "$UNISON_LOG_PHOTOS" "Upload Fotos" "up" format_unison_details "$UNISON_LOG_PHOTOS" "Download Fotos" "down" fi else log "Keine Änderungen – alles aktuell." fi else log_step "Sync abgeschlossen" log " Geändert: $TOTAL_CHANGED ($CHANGED_DB DB + $CHANGED_PHOTOS Fotos)" if [ "$TOTAL_CHANGED" -gt 0 ]; then notify-send "Darktable Sync" \ "↕ $TOTAL_CHANGED Datei(en) synchronisiert" -t 10000 else log "Keine Änderungen – alles aktuell." fi if [ "$SHOW_NOTIFY_START_STOP" = true ]; then notify-send "Darktable Sync" "Sync abgeschlossen." -t 3000 fi fi