186 lines
7.4 KiB
Bash
186 lines
7.4 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# Упорядочивание папок игр в common/ (HDD 4 TB): GameName/GameName/ (файлы) + appmanifest в GameName/,
|
|
# затем перенос всех папок GameName из common/ на уровень выше (HDD 4 TB). Итог: HDD 4 TB/GameName/GameName/, HDD 4 TB/GameName/appmanifest_*.acf, common/ пустая.
|
|
# AppId берётся по имени папки из steam_library_with_sizes.json (резолвер resolve_steam_appid.py).
|
|
# Ручные сопоставления (CM3, Darksiders 3, DOOMEternal и др.) — в resolve_steam_appid.py MANUAL_MAP.
|
|
#
|
|
# Запуск в CT 101. Корневая директория: /mnt/nextcloud-extra/common/ (PARENT = /mnt/nextcloud-extra = HDD 4 TB).
|
|
#
|
|
# Подготовка: скопировать в контейнер скрипты и JSON (в одну папку, например /opt/scripts/):
|
|
# pct push 101 homelab/scripts/reorganize-games-common.sh /opt/scripts/
|
|
# pct push 101 homelab/scripts/resolve_steam_appid.py /opt/scripts/
|
|
# pct push 101 homelab/scripts/steam_library_with_sizes.json /opt/scripts/
|
|
#
|
|
# Dry-run (ничего не меняет, только вывод; в конце — папки без совпадений):
|
|
# ssh root@192.168.1.150 'pct exec 101 -- bash /opt/scripts/reorganize-games-common.sh /mnt/nextcloud-extra/common 1 /opt/scripts/resolve_steam_appid.py /opt/scripts/steam_library_with_sizes.json'
|
|
#
|
|
# Реальное выполнение (второй аргумент 0):
|
|
# ssh root@192.168.1.150 'pct exec 101 -- bash /opt/scripts/reorganize-games-common.sh /mnt/nextcloud-extra/common 0 /opt/scripts/resolve_steam_appid.py /opt/scripts/steam_library_with_sizes.json'
|
|
#
|
|
set -euo pipefail
|
|
|
|
ROOT="${1:?Usage: $0 <ROOT_DIR> [DRY_RUN=1] [RESOLVER_SCRIPT] [LIBRARY_JSON]}"
|
|
DRY_RUN="${2:-1}"
|
|
RESOLVER_SCRIPT="${3:-}"
|
|
LIBRARY_JSON="${4:-}"
|
|
[[ -z "$RESOLVER_SCRIPT" ]] && [[ -f ./resolve_steam_appid.py ]] && RESOLVER_SCRIPT="./resolve_steam_appid.py"
|
|
[[ -n "$RESOLVER_SCRIPT" ]] && [[ -z "$LIBRARY_JSON" ]] && LIBRARY_JSON="$(dirname "$RESOLVER_SCRIPT")/steam_library_with_sizes.json"
|
|
[[ -z "$RESOLVER_SCRIPT" ]] && {
|
|
echo "ERROR: resolve_steam_appid.py not found. Pass path as 3rd arg or run from script dir."
|
|
exit 1
|
|
}
|
|
[[ ! -f "$RESOLVER_SCRIPT" ]] && {
|
|
echo "ERROR: resolver script not found: $RESOLVER_SCRIPT"
|
|
exit 1
|
|
}
|
|
[[ -z "$LIBRARY_JSON" ]] || [[ ! -f "$LIBRARY_JSON" ]] && {
|
|
echo "ERROR: steam_library_with_sizes.json not found. Pass path as 4th arg or put next to resolver."
|
|
exit 1
|
|
}
|
|
|
|
echo "=== ROOT: $ROOT | DRY_RUN: $DRY_RUN | Resolver: $RESOLVER_SCRIPT | Library: $LIBRARY_JSON ==="
|
|
echo ""
|
|
|
|
if [[ ! -d "$ROOT" ]]; then
|
|
echo "ERROR: not a directory: $ROOT"
|
|
exit 1
|
|
fi
|
|
|
|
PARENT_DIR="$(dirname "$ROOT")"
|
|
echo "Папки после обработки будут перенесены: $ROOT -> $PARENT_DIR (уровень выше)"
|
|
echo ""
|
|
|
|
MISSING_APPID=()
|
|
MISSING_MANIFEST=()
|
|
RESOLVED=()
|
|
PROCESSED_DIRS=()
|
|
|
|
while IFS= read -r -d '' dir; do
|
|
base=$(basename "$dir")
|
|
[[ "$base" == "lost+found" ]] && continue
|
|
|
|
appid=""
|
|
appid=$(python3 "$RESOLVER_SCRIPT" "$LIBRARY_JSON" "$base" 2>/dev/null || true)
|
|
if [[ -z "$appid" ]]; then
|
|
MISSING_APPID+=("$base")
|
|
echo "[$base] appId не найден по имени — пропуск"
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
manifest="$ROOT/appmanifest_${appid}.acf"
|
|
# Манифест может лежать в корне common/ или на уровень выше (steamapps/)
|
|
if [[ ! -f "$manifest" ]]; then
|
|
manifest_parent="$(dirname "$ROOT")/appmanifest_${appid}.acf"
|
|
if [[ -f "$manifest_parent" ]]; then
|
|
manifest="$manifest_parent"
|
|
else
|
|
MISSING_MANIFEST+=("$base -> appmanifest_${appid}.acf")
|
|
fi
|
|
fi
|
|
|
|
inner="$dir/$base"
|
|
if [[ -d "$inner" ]]; then
|
|
echo "[$base] (уже есть вложенная $base/) appId=$appid"
|
|
for item in "$dir"/*; do
|
|
[[ -e "$item" ]] || continue
|
|
iname=$(basename "$item")
|
|
[[ "$iname" == "$base" ]] && continue
|
|
[[ "$iname" == appmanifest_*.acf ]] && continue
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
echo " [DRY-RUN] mv '$item' -> '$inner/'"
|
|
else
|
|
mv "$item" "$inner/"
|
|
echo " mv $iname -> $base/"
|
|
fi
|
|
done
|
|
if [[ -f "$manifest" ]]; then
|
|
dest_manifest="$dir/appmanifest_${appid}.acf"
|
|
if [[ "$manifest" != "$dest_manifest" ]]; then
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
echo " [DRY-RUN] cp/mv '$manifest' -> '$dest_manifest'"
|
|
else
|
|
cp "$manifest" "$dest_manifest" 2>/dev/null || mv "$manifest" "$dest_manifest"
|
|
echo " appmanifest_${appid}.acf -> $dir/"
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
echo "[$base] (создать $base/$base/ и перенести файлы) appId=$appid"
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
echo " [DRY-RUN] mkdir -p '$inner'"
|
|
else
|
|
mkdir -p "$inner"
|
|
fi
|
|
for item in "$dir"/*; do
|
|
[[ -e "$item" ]] || continue
|
|
iname=$(basename "$item")
|
|
[[ "$iname" == "$base" ]] && continue
|
|
[[ "$iname" == appmanifest_*.acf ]] && continue
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
echo " [DRY-RUN] mv '$item' -> '$inner/'"
|
|
else
|
|
mv "$item" "$inner/"
|
|
echo " mv $iname -> $base/"
|
|
fi
|
|
done
|
|
if [[ -f "$manifest" ]]; then
|
|
dest_manifest="$dir/appmanifest_${appid}.acf"
|
|
if [[ "$manifest" != "$dest_manifest" ]]; then
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
echo " [DRY-RUN] cp/mv '$manifest' -> '$dest_manifest'"
|
|
else
|
|
cp "$manifest" "$dest_manifest" 2>/dev/null || mv "$manifest" "$dest_manifest"
|
|
echo " appmanifest_${appid}.acf -> $dir/"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
RESOLVED+=("$base -> $appid")
|
|
PROCESSED_DIRS+=("$base")
|
|
echo ""
|
|
done < <(find "$ROOT" -maxdepth 1 -type d ! -path "$ROOT" -print0 | sort -z)
|
|
|
|
echo "=== Перенос папок из common/ на уровень выше (HDD 4 TB) ==="
|
|
for base in "${PROCESSED_DIRS[@]}"; do
|
|
src="$ROOT/$base"
|
|
dst="$PARENT_DIR/$base"
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
echo " [DRY-RUN] mv '$src' -> '$PARENT_DIR/'"
|
|
else
|
|
if mv "$src" "$PARENT_DIR/" 2>/dev/null; then
|
|
echo " mv $base -> $PARENT_DIR/"
|
|
else
|
|
# Цель уже существует (например, дубликат игры) — сливаем содержимое и удаляем источник
|
|
if [[ -d "$dst" ]]; then
|
|
inner="$src/$base"
|
|
if [[ -d "$inner" ]]; then
|
|
mkdir -p "$dst/$base"
|
|
for item in "$inner"/*; do [[ -e "$item" ]] && mv "$item" "$dst/$base/"; done
|
|
rmdir "$inner" 2>/dev/null || true
|
|
fi
|
|
for f in "$src"/appmanifest_*.acf; do [[ -f "$f" ]] && cp -n "$f" "$dst/" 2>/dev/null || true; done
|
|
rm -rf "$src"
|
|
echo " merge $base -> $PARENT_DIR/ (target existed)"
|
|
else
|
|
echo " ERROR: could not move $base"
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
echo ""
|
|
|
|
echo "=== Папки, для которых не найден appId ==="
|
|
for x in "${MISSING_APPID[@]}"; do echo " $x"; done
|
|
echo ""
|
|
echo "=== Итог: игры в $PARENT_DIR/GameName/GameName/, манифесты в $PARENT_DIR/GameName/, common/ пустая ==="
|
|
echo ""
|
|
echo "=== Нет файла appmanifest (игра найдена по имени) ==="
|
|
for x in "${MISSING_MANIFEST[@]}"; do echo " $x"; done
|
|
echo ""
|
|
echo "=== Сопоставления имя -> appId ==="
|
|
for x in "${RESOLVED[@]}"; do echo " $x"; done
|
|
echo ""
|
|
echo "=== Конец (DRY_RUN=$DRY_RUN) ==="
|