#!/usr/bin/env bash # Gemeinsame Hilfsfunktionen fuer darktable-sync Scripts. # Dieses Script wird per `source` eingebunden, nicht direkt ausgefuehrt. CONFIG_DIR="$HOME/.config/darktable-sync" load_config() { local env_file="$CONFIG_DIR/.env" if [ ! -f "$env_file" ]; then echo "Fehler: Konfiguration nicht gefunden: $env_file" >&2 echo "Vorlage kopieren mit: cp .env.example $env_file" >&2 exit 1 fi # Berechtigungen pruefen: .env darf nicht world-readable sein local perms perms=$(stat -c '%a' "$env_file" 2>/dev/null || stat -f '%A' "$env_file" 2>/dev/null) if [[ "${perms: -1}" != "0" ]]; then echo "Warnung: $env_file ist world-readable. Empfehlung: chmod 600 $env_file" >&2 fi # Zeilen mit Shell-Operatoren abweisen (Kommentare und Leerzeilen ignorieren) if grep -vE '^\s*#|^\s*$' "$env_file" | grep -qE '[;|&`]'; then echo "Fehler: $env_file enthaelt unerlaubte Zeichen (; | & \`). Bitte pruefen." >&2 exit 1 fi # shellcheck source=/dev/null . "$env_file" } require_var() { local var_name="$1" if [ -z "${!var_name:-}" ]; then echo "Fehler: Variable '$var_name' ist nicht gesetzt in $CONFIG_DIR/.env" >&2 exit 1 fi } validate_path() { local var_name="$1" value="${!1:-}" # Pfade duerfen keine Shell-Sonderzeichen oder Path-Traversal enthalten if echo "$value" | grep -qE "['\";|&\`\$()\\\\]" || [[ "$value" == *".."* ]]; then echo "Fehler: '$var_name' enthaelt unerlaubte Zeichen: $value" >&2 exit 1 fi } validate_config() { require_var SERVER_IP require_var SERVER_USER require_var SERVER_SSH_PORT require_var SERVER_DB_DIR require_var SERVER_PHOTO_DIR require_var LOCAL_DARKTABLE_DB_DIR require_var LOCAL_PHOTO_DIR require_var SYNC_BIN require_var DARKTABLE_BIN validate_path SERVER_DB_DIR validate_path SERVER_PHOTO_DIR validate_path LOCAL_DARKTABLE_DB_DIR validate_path LOCAL_PHOTO_DIR # DARKTABLE_BIN: basename muss 'darktable' sein if [[ "$(basename "$DARKTABLE_BIN")" != "darktable" ]]; then echo "Fehler: DARKTABLE_BIN muss auf 'darktable' zeigen, nicht auf '$(basename "$DARKTABLE_BIN")'." >&2 exit 1 fi } check_dependency() { local cmd="$1" pkg="${2:-$1}" if ! command -v "$cmd" &>/dev/null; then echo "Fehler: '$cmd' ist nicht installiert." >&2 echo "Installieren mit: sudo apt install $pkg" >&2 exit 1 fi } log() { echo "$*" } log_step() { echo "" echo "=== $* ===" } log_error() { echo "FEHLER: $*" >&2 } classify_filetype() { local file="$1" local ext="${file##*.}"; ext="${ext,,}" case "$ext" in jpg|jpeg|png|tif|tiff|dng|cr2|cr3|nef|arw|orf|rw2|raf|raw) echo "Foto" ;; xmp) echo "XMP" ;; db|bak) echo "Datenbank" ;; mp4|mov|avi|mkv|mts|m2ts) echo "Video" ;; *) echo "Sonstiges" ;; esac } format_rsync_details() { local log_file="$1" direction_label="$2" direction="$3" [ -f "$log_file" ] || return 0 local prefix; [ "$direction" = "up" ] && prefix=">f" || prefix="/dev/null \ | sed 's/^[^ ]* *//' | sort) || true [ -n "$files" ] || continue local typ typed for typ in Foto XMP Datenbank Video Sonstiges; do typed=$(echo "$files" | while IFS= read -r f; do [ "$(classify_filetype "$f")" = "$typ" ] && echo " $f" done) [ -n "$typed" ] || continue log_step "$direction_label – $typ ($label)" echo "$typed" done done </dev/null || true fi } confirm_dry_run() { [ "${DRY_RUN_SKIP_CONFIRM:-0}" = "1" ] && return 0 ask_user "Darktable Sync – Trockenlauf" \ "Trockenlauf starten?\n\nEs werden keine Dateien verändert oder übertragen." } ssh_server() { ssh -o ConnectTimeout=5 -o BatchMode=yes \ -p "$SERVER_SSH_PORT" "$SERVER_USER@$SERVER_IP" "$@" } # Liefert den Unix-Timestamp (mtime) von library.db auf dem Server, oder "0" wenn nicht vorhanden. server_db_mtime() { ssh_server "stat -c '%Y' '$SERVER_DB_DIR/library.db' 2>/dev/null || echo 0" } save_sync_token() { echo "$1" > "$CONFIG_DIR/sync_token" } read_sync_token() { cat "$CONFIG_DIR/sync_token" 2>/dev/null || echo "" } server_reachable() { ssh_server true 2>/dev/null } ask_user() { local title="$1" text="$2" ans if [ "${DARKTABLE_SYNC_MODE:-}" = "gui" ] && command -v zenity &>/dev/null; then zenity --question --title="$title" --text="$text" 2>/dev/null return $? elif [ "${DARKTABLE_SYNC_MODE:-}" = "gui" ] && command -v kdialog &>/dev/null; then kdialog --title "$title" --yesno "$text" 2>/dev/null return $? else printf '%b\n' "$text" read -r -p "[j/N] " ans || true [[ "$ans" =~ ^[jJyY] ]] return $? fi } # Fragt den User wie mit einem Sync-Token-Konflikt umgegangen werden soll. # Gibt "download", "upload" oder "abort" aus. ask_conflict_resolution() { local TITLE="Darktable Sync – Konflikt" local EXPLAIN="Ein anderer Rechner hat die Datenbank seit deinem letzten Sync verändert.\nDeine lokalen Änderungen wurden noch NICHT auf den Server übertragen.\n\nWas soll passieren?" if [ "${DARKTABLE_SYNC_MODE:-}" = "gui" ] && command -v zenity &>/dev/null; then local choice choice=$(zenity --list \ --title="$TITLE" \ --text="$EXPLAIN" \ --radiolist \ --column="" --column="Aktion" --column="Beschreibung" \ TRUE "Herunterladen" "Server-Stand übernehmen (empfohlen)" \ FALSE "Hochladen erzwingen" "Lokale Version auf Server schreiben – Server-Änderungen gehen verloren!" \ FALSE "Abbrechen" "Nichts tun – Sync wird übersprungen" \ --width=520 --height=260 2>/dev/null) || true case "$choice" in "Hochladen erzwingen") echo "upload" ;; "Abbrechen") echo "abort" ;; *) echo "download" ;; esac elif [ "${DARKTABLE_SYNC_MODE:-}" = "gui" ] && command -v kdialog &>/dev/null; then local btn btn=$(kdialog --title "$TITLE" \ --menu "$EXPLAIN" \ download "Herunterladen (empfohlen)" \ upload "Hochladen erzwingen (Server-Änderungen gehen verloren!)" \ abort "Abbrechen" 2>/dev/null) || true case "$btn" in upload|abort) echo "$btn" ;; *) echo "download" ;; esac else echo "" echo "=== $TITLE ===" echo "Ein anderer Rechner hat die Datenbank seit deinem letzten Sync verändert." echo "Deine lokalen Änderungen wurden noch NICHT auf den Server übertragen." echo "" echo " 1) Herunterladen (empfohlen) – Server-Stand übernehmen" echo " 2) Hochladen erzwingen – lokale Version gewinnt, Server-Änderungen gehen verloren" echo " 3) Abbrechen" local ans read -r -p "Auswahl [1/2/3, Standard: 1]: " ans || true case "$ans" in 2) echo "upload" ;; 3) echo "abort" ;; *) echo "download" ;; esac fi }