Compare commits

..

6 Commits

Author SHA1 Message Date
skymike03
f9d95b9a2d v2.3.1.8 (2025.11.08)
Merge pull request [#30](https://github.com/RetroGameSets/RGSX/issues/30) from SeeThruHead/main

Refactor Docker setup with proper volume separation and backwards compatibility

Implemented environment variable-based configuration to support both Docker
and traditional Batocera/RetroBat installations with a single codebase.
2025-11-08 23:01:19 +01:00
RGS
2033eb2f76 Merge pull request #30 from SeeThruHead/main
Refactor Docker setup with proper volume separation and backwards com…
2025-11-09 00:04:38 +01:00
skymike03
61b615f4c7 v2.3.1.7
- correct typo error in xiso linux
2025-11-05 23:04:54 +01:00
Shane Keulen
f1c4955670 Merge branch 'RetroGameSets:main' into main 2025-11-05 14:28:48 -05:00
skymike03
03d64d4401 v2.3.1.6 (2025.11.05)
- Replace xdvdfs by extract_xiso because conversion not working in all xbox games
2025-11-05 05:33:18 +01:00
shane keulen
5569238e55 Refactor Docker setup with proper volume separation and backwards compatibility
Implemented environment variable-based configuration to support both Docker
and traditional Batocera/RetroBat installations with a single codebase.

Key Changes:
- Added RGSX_CONFIG_DIR and RGSX_DATA_DIR environment variables
- Separate /config and /data volumes in Docker mode
- App files now copied into container at build time (not runtime sync)
- Simplified directory structure (removed __downloads concept)
- Maintained 100% backwards compatibility with non-Docker installations

File Structure by Mode:

| Location        | Docker Mode     | Traditional Mode                   |
|-----------------|-----------------|----------------------------------- |
| Settings/Config | /config/        | /userdata/saves/ports/rgsx/        |
| Game Lists      | /config/games/  | /userdata/saves/ports/rgsx/games/  |
| Images          | /config/images/ | /userdata/saves/ports/rgsx/images/ |
| Logs            | /config/logs/   | /userdata/roms/ports/RGSX/logs/    |
| ROMs            | /data/roms/     | /userdata/roms/                    |

Detection:
- Docker mode: Activated when RGSX_CONFIG_DIR or RGSX_DATA_DIR is set
- Traditional mode: Default when no Docker env vars present

Tested and verified working in both modes.
2025-11-04 21:36:30 -05:00
10 changed files with 344 additions and 152 deletions

2
.gitignore vendored
View File

@@ -21,3 +21,5 @@ Info.txt
# Docker test data
data/
docker-compose.test.yml
config/

View File

@@ -5,28 +5,29 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
p7zip-full \
unrar-free \
curl \
rsync \
gosu \
&& rm -rf /var/lib/apt/lists/*
# Create required directories
RUN mkdir -p /userdata/saves/ports/rgsx \
&& mkdir -p /userdata/roms/ports \
&& mkdir -p /app
# Create app directory
RUN mkdir -p /app
# Copy RGSX application files to /app (will be copied to volume at runtime)
# Copy RGSX application files to /app
COPY ports/RGSX/ /app/RGSX/
# Copy entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/
COPY docker/docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Install Python dependencies
# pygame is imported in some modules even in headless mode, so we include it
RUN pip install --no-cache-dir requests pygame
# Set environment to headless mode
ENV RGSX_HEADLESS=1
# Set environment variables for Docker mode
# These tell RGSX to use /config for settings and /data for ROMs
ENV RGSX_HEADLESS=1 \
RGSX_APP_DIR=/app \
RGSX_CONFIG_DIR=/config \
RGSX_DATA_DIR=/data
# Expose web interface port
EXPOSE 5000
@@ -35,6 +36,9 @@ EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:5000/ || exit 1
# Entrypoint copies app to volume, then runs command
# Set working directory
WORKDIR /app/RGSX
# Entrypoint handles user permissions and directory setup
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["python", "rgsx_web.py", "--host", "0.0.0.0", "--port", "5000"]

View File

@@ -5,19 +5,16 @@ Run RGSX as a web-only service without the Pygame UI. Perfect for homelab/server
## Quick Start
```bash
# Build the image
docker build -t rgsx .
# Using docker-compose (recommended)
docker-compose up -d
# Run with docker
# Or build and run manually
docker build -f docker/Dockerfile -t rgsx .
docker run -d \
--name rgsx \
-p 5000:5000 \
-e PUID=99 \
-e PGID=100 \
-e RGSX_HEADLESS=1 \
-v ./data/saves:/userdata/saves/ports/rgsx \
-v ./data/roms:/userdata/roms/ports \
-v ./data/logs:/userdata/roms/ports/RGSX/logs \
-v ./config:/config \
-v ./data:/data \
rgsx
# Access the web interface
@@ -28,65 +25,63 @@ open http://localhost:5000
- Runs RGSX web server in headless mode (no Pygame UI)
- Web interface accessible from any browser
- ROMs and settings persist in `./data/` volumes
- Container restarts automatically
- Config persists in `/config` volume (settings, metadata, history)
- ROMs download to `/data/roms/{platform}/` and extract there
- Environment variables pre-configured (no manual setup needed)
## Environment Variables
**Pre-configured in the container (no need to set these):**
- `RGSX_HEADLESS=1` - Runs in headless mode
- `RGSX_CONFIG_DIR=/config` - Config location
- `RGSX_DATA_DIR=/data` - Data location
**Optional (only if needed):**
- `PUID` - User ID for file ownership (default: root)
- `PGID` - Group ID for file ownership (default: root)
## Configuration
### Docker Compose
See `docker-compose.example.yml` for a complete example configuration.
### User Permissions (Important!)
**For SMB mounts (Unraid, Windows shares):**
Don't set PUID/PGID. The container runs as root, and the SMB server maps files to your authenticated user.
```bash
docker run \
-e RGSX_HEADLESS=1 \
...
```
- Don't set PUID/PGID
- The container runs as root, and the SMB server maps files to your authenticated user
**For NFS/local storage:**
- Set PUID and PGID to match your host user (files will be owned by that user)
- Find your user ID: `id -u` and `id -g`
Set PUID and PGID to match your host user. Files will be owned by that user.
### Volumes
```bash
docker run \
-e PUID=1000 \
-e PGID=1000 \
-e RGSX_HEADLESS=1 \
...
```
Two volumes are used:
**Find your user ID:**
```bash
id -u # Your UID
id -g # Your GID
```
**`/config`** - Configuration and metadata
- `rgsx_settings.json` - Settings
- `games/` - Platform game database files (JSON)
- `images/` - Game cover art
- `history.json` - Download history
- `logs/` - Application logs
- `*.txt` - API keys
### Change Port
```bash
docker run -p 8080:5000 ... # Access on port 8080
```
### Custom ROM Location
Map to your existing ROM collection:
```bash
docker run -v /your/existing/roms:/userdata/roms/ports ...
```
**`/data`** - ROM storage
- `roms/` - ROMs by platform (snes/, nes/, psx/, etc.) - downloads extract here
### API Keys
Add your download service API keys to `./data/saves/`:
Add your download service API keys to `./config/`:
```bash
# Add your API key (just the key, no extra text)
echo "YOUR_KEY_HERE" > ./data/saves/1FichierAPI.txt
echo "YOUR_KEY_HERE" > ./config/1FichierAPI.txt
# Optional: AllDebrid/RealDebrid fallbacks
echo "YOUR_KEY" > ./data/saves/AllDebridAPI.txt
echo "YOUR_KEY" > ./data/saves/RealDebridAPI.txt
# Optional: AllDebrid/RealDebrid
echo "YOUR_KEY" > ./config/AllDebridAPI.txt
echo "YOUR_KEY" > ./config/RealDebridAPI.txt
# Restart to apply
docker restart rgsx
@@ -112,14 +107,29 @@ docker stop rgsx && docker rm rgsx
## Directory Structure
**On Host:**
```
RGSX/
├── data/ # Created on first run
│ ├── saves/ # Settings, history, API keys
│ ├── roms/ # Downloaded ROMs
── logs/ # Application logs
├── Dockerfile
└── docker-compose.yml
./
├── config/ # Config volume (created on first run)
│ ├── rgsx_settings.json
│ ├── games/ # Platform game database (JSON)
── images/ # Platform images
│ ├── logs/ # Application logs
│ └── *.txt # API keys (1FichierAPI.txt, etc.)
└── data/
└── roms/ # ROMs by platform
├── snes/
├── n64/
└── ...
```
**In Container:**
```
/app/RGSX/ # Application code
/config/ # Mapped to ./config on host
└── games/, images/, logs/, etc.
/data/ # Mapped to ./data on host
└── roms/ # ROM downloads go here
```
## How It Works
@@ -137,22 +147,15 @@ The container creates files with the UID/GID specified by PUID/PGID environment
docker run -e PUID=1000 -e PGID=1000 ...
```
**Changed PUID/PGID and container won't start:**
**Changed PUID/PGID and permission errors:**
When you change PUID/PGID, old files with different ownership will cause rsync to fail. You MUST fix ownership on the storage server:
Fix ownership of your volumes:
```bash
# On your NAS/Unraid (via SSH), either:
# Option 1: Delete old files (easiest)
rm -rf /mnt/user/roms/rgsx/roms/ports/RGSX/*
# Option 2: Change ownership to new PUID/PGID
chown -R 1000:1000 /mnt/user/roms/rgsx/roms/ports/RGSX/
# Fix ownership to match new PUID/PGID
sudo chown -R 1000:1000 ./config ./data
```
Then restart the container.
**Port already in use:**
```bash
docker run -p 8080:5000 ... # Use port 8080 instead

View File

@@ -0,0 +1,29 @@
version: '3.8'
services:
rgsx:
# Option 1: Build from source
build:
context: ..
dockerfile: docker/Dockerfile
# Option 2: Use pre-built image (push to your own registry)
# image: your-registry/rgsx:latest
container_name: rgsx
restart: unless-stopped
ports:
- "5000:5000"
volumes:
- ./config:/config
- ./data:/data
# Optional: Set PUID/PGID for NFS/local storage (not needed for SMB mounts)
# environment:
# - PUID=1000
# - PGID=1000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s

View File

@@ -1,10 +1,12 @@
#!/bin/bash
set -e
echo "=== RGSX Docker Container Startup ==="
# If PUID/PGID are set, create user and run as that user
# If not set, run as root (works for SMB mounts)
if [ -n "$PUID" ] && [ -n "$PGID" ]; then
echo "=== Creating user with PUID=$PUID, PGID=$PGID ==="
echo "Creating user with PUID=$PUID, PGID=$PGID..."
# Create group if it doesn't exist
if ! getent group $PGID >/dev/null 2>&1; then
@@ -16,43 +18,28 @@ if [ -n "$PUID" ] && [ -n "$PGID" ]; then
useradd -u $PUID -g $PGID -m -s /bin/bash rgsx
fi
# Fix ownership of app files
chown -R $PUID:$PGID /app /userdata 2>/dev/null || true
echo "=== Running as user $(id -un $PUID) (UID=$PUID, GID=$PGID) ==="
echo "Running as user $(id -un $PUID) (UID=$PUID, GID=$PGID)"
RUN_USER="gosu rgsx"
else
echo "=== Running as root (no PUID/PGID set) - for SMB mounts ==="
echo "Running as root (no PUID/PGID set) - suitable for SMB mounts"
RUN_USER=""
fi
# Always sync RGSX app code to the mounted volume (for updates)
echo "Syncing RGSX app code to /userdata/roms/ports/RGSX..."
$RUN_USER mkdir -p /userdata/roms/ports/RGSX
# Create necessary directories
# /config needs logs directory, app will create others (like images/, games/) as needed
# /data needs roms directory
echo "Setting up directories..."
$RUN_USER mkdir -p /config/logs
$RUN_USER mkdir -p /data/roms
# Try rsync
if ! $RUN_USER rsync -av --delete /app/RGSX/ /userdata/roms/ports/RGSX/ 2>&1; then
echo ""
echo "=========================================="
echo "WARNING: rsync partially failed!"
echo "=========================================="
echo "Some files may not have synced. Container will continue for debugging."
echo ""
if [ -n "$PUID" ] && [ -n "$PGID" ]; then
echo "If using SMB, try removing PUID/PGID to run as root"
fi
echo ""
# Fix ownership of volumes if PUID/PGID are set
if [ -n "$PUID" ] && [ -n "$PGID" ]; then
echo "Setting ownership on volumes..."
chown -R $PUID:$PGID /config /data 2>/dev/null || true
fi
echo "RGSX app code sync attempted."
# Create Batocera folder structure only if folders don't exist
$RUN_USER mkdir -p /userdata/saves/ports/rgsx/images
$RUN_USER mkdir -p /userdata/saves/ports/rgsx/games
$RUN_USER mkdir -p /userdata/roms/ports/RGSX/logs
# Create default settings with show_unsupported_platforms enabled if config doesn't exist
SETTINGS_FILE="/userdata/saves/ports/rgsx/rgsx_settings.json"
SETTINGS_FILE="/config/rgsx_settings.json"
if [ ! -f "$SETTINGS_FILE" ]; then
echo "Creating default settings with all platforms visible..."
$RUN_USER bash -c "cat > '$SETTINGS_FILE' << 'EOF'
@@ -60,11 +47,15 @@ if [ ! -f "$SETTINGS_FILE" ]; then
\"show_unsupported_platforms\": true
}
EOF"
echo "Default settings created!"
echo "Default settings created at $SETTINGS_FILE"
fi
# Run the command
cd /userdata/roms/ports/RGSX
echo "=== Starting RGSX Web Server ==="
echo "Config directory: /config"
echo "ROMs directory: /data/roms"
echo "======================================"
# Run the command from the working directory (/app/RGSX set in Dockerfile)
if [ -z "$RUN_USER" ]; then
exec "$@"
else

Binary file not shown.

Binary file not shown.

View File

@@ -13,7 +13,7 @@ except Exception:
pygame = None # type: ignore
# Version actuelle de l'application
app_version = "2.3.1.5"
app_version = "2.3.1.8"
def get_application_root():
@@ -28,29 +28,164 @@ def get_application_root():
# Si __file__ n'est pas défini (par exemple, exécution dans un REPL)
return os.path.abspath(os.getcwd())
### CONSTANTES DES CHEMINS DE BASE
#
# This application supports two deployment modes:
#
# ===== TRADITIONAL MODE (Batocera/Retrobat) =====
# No environment variables set. Everything is derived from the app code location.
#
# Structure:
# /userdata/
# ├── roms/
# │ ├── ports/RGSX/ ← APP_FOLDER (application code)
# │ ├── snes/ ← ROMs organized by platform
# │ └── ...
# └── saves/ports/rgsx/ ← SAVE_FOLDER (config & data)
# ├── rgsx_settings.json ← settings file
# ├── images/ ← scraped metadata/covers
# ├── games/ ← temporary download location
# ├── history.json ← download history
# └── ...
#
# ===== DOCKER MODE =====
# Enabled by setting RGSX_CONFIG_DIR and/or RGSX_DATA_DIR environment variables.
# This separates the app code, configuration, and data into different directories
# that can be mounted as Docker volumes.
#
# Environment Variables:
# RGSX_APP_DIR (optional): Where app code lives (defaults to /app in Docker)
# RGSX_CONFIG_DIR: Where config/settings/metadata live (mount to /config volume)
# RGSX_DATA_DIR: Where ROMs and data live (mount to /data volume)
#
# Structure:
# /app/RGSX/ ← APP_FOLDER (application code)
# /config/ ← CONFIG_FOLDER / SAVE_FOLDER
# ├── rgsx_settings.json ← settings file
# ├── games/ ← GAME_LISTS_FOLDER (platform game database JSONs)
# ├── images/ ← scraped metadata/covers
# ├── logs/ ← application logs
# ├── history.json ← download history
# └── ...
# /data/
# └── roms/ ← ROMS_FOLDER
# ├── snes/ ← ROMs download here, extract, delete zip
# ├── nes/
# └── ...
#
# Single Volume Mode:
# If only RGSX_CONFIG_DIR is set (no RGSX_DATA_DIR), both config and data
# will use the same directory.
# Chemins de base
APP_FOLDER = os.path.join(get_application_root(), "RGSX")
USERDATA_FOLDER = os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER))) # remonte de /userdata/roms/ports/rgsx à /userdata ou \Retrobat
SAVE_FOLDER = os.path.join(USERDATA_FOLDER, "saves", "ports", "rgsx")
# Check for Docker mode environment variables
_docker_app_dir = os.environ.get("RGSX_APP_DIR", "").strip()
_docker_config_dir = os.environ.get("RGSX_CONFIG_DIR", "").strip()
_docker_data_dir = os.environ.get("RGSX_DATA_DIR", "").strip()
# Determine if we're running in Docker mode
_is_docker_mode = bool(_docker_config_dir or _docker_data_dir)
if _is_docker_mode:
# ===== DOCKER MODE =====
# App code location (can be overridden, defaults to file location)
if _docker_app_dir:
APP_FOLDER = os.path.join(_docker_app_dir, "RGSX")
else:
APP_FOLDER = os.path.join(get_application_root(), "RGSX")
# Config directory: where rgsx_settings.json and metadata live
if _docker_config_dir:
CONFIG_FOLDER = _docker_config_dir
SAVE_FOLDER = _docker_config_dir
else:
# Fallback: derive from traditional structure
USERDATA_FOLDER = os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER)))
CONFIG_FOLDER = os.path.join(USERDATA_FOLDER, "saves", "ports", "rgsx")
SAVE_FOLDER = CONFIG_FOLDER
# Data directory: where ROMs live
# If not set, fallback to CONFIG_FOLDER (single volume mode)
if _docker_data_dir:
DATA_FOLDER = _docker_data_dir
elif _docker_config_dir:
DATA_FOLDER = _docker_config_dir
else:
USERDATA_FOLDER = os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER)))
DATA_FOLDER = USERDATA_FOLDER
# For backwards compatibility with code that references USERDATA_FOLDER
USERDATA_FOLDER = DATA_FOLDER
else:
# ===== TRADITIONAL MODE =====
# Derive all paths from app location using original logic
APP_FOLDER = os.path.join(get_application_root(), "RGSX")
# Go up 3 directories from APP_FOLDER to find USERDATA
# Example: /userdata/roms/ports/RGSX -> /userdata
USERDATA_FOLDER = os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER)))
# Config and data are both under USERDATA in traditional mode
CONFIG_FOLDER = os.path.join(USERDATA_FOLDER, "saves", "ports", "rgsx")
SAVE_FOLDER = CONFIG_FOLDER
DATA_FOLDER = USERDATA_FOLDER
# ROMS_FOLDER - Can be customized via rgsx_settings.json
#
# The "roms_folder" setting in rgsx_settings.json supports three formats:
#
# 1. Empty string or not set:
# Uses default location:
# - Docker mode: /data/roms
# - Traditional mode: /userdata/roms
#
# 2. Absolute path (e.g., "/my/custom/roms"):
# Uses the path exactly as specified (must exist)
# Works in both Docker and Traditional modes
#
# 3. Relative path (e.g., "my_roms" or "../custom_roms"):
# Resolved relative to:
# - Docker mode: DATA_FOLDER (e.g., /data/my_roms)
# - Traditional mode: USERDATA_FOLDER (e.g., /userdata/my_roms)
#
# If the specified path doesn't exist, falls back to default.
# Default ROM location
_default_roms_folder = os.path.join(DATA_FOLDER if _is_docker_mode else USERDATA_FOLDER, "roms")
# ROMS_FOLDER - Charger depuis rgsx_settings.json si défini, sinon valeur par défaut
_default_roms_folder = os.path.join(USERDATA_FOLDER, "roms")
try:
# Import tardif pour éviter les dépendances circulaires
# Try to load custom roms_folder from settings
_settings_path = os.path.join(SAVE_FOLDER, "rgsx_settings.json")
if os.path.exists(_settings_path):
import json
with open(_settings_path, 'r', encoding='utf-8') as _f:
_settings = json.load(_f)
_custom_roms = _settings.get("roms_folder", "").strip()
if _custom_roms and os.path.isdir(_custom_roms):
ROMS_FOLDER = _custom_roms
if _custom_roms:
# Check if it's an absolute path
if os.path.isabs(_custom_roms):
# Absolute path: use as-is if directory exists
if os.path.isdir(_custom_roms):
ROMS_FOLDER = _custom_roms
else:
ROMS_FOLDER = _default_roms_folder
else:
# Relative path: resolve relative to DATA_FOLDER (docker) or USERDATA_FOLDER (traditional)
_base = DATA_FOLDER if _is_docker_mode else USERDATA_FOLDER
_resolved = os.path.join(_base, _custom_roms)
if os.path.isdir(_resolved):
ROMS_FOLDER = _resolved
else:
ROMS_FOLDER = _default_roms_folder
else:
# Empty: use default
ROMS_FOLDER = _default_roms_folder
else:
# Settings file doesn't exist yet: use default
ROMS_FOLDER = _default_roms_folder
except Exception as _e:
ROMS_FOLDER = _default_roms_folder
@@ -64,7 +199,15 @@ logger = logging.getLogger(__name__)
download_queue = [] # Liste de dicts: {url, platform, game_name, ...}
# Indique si un téléchargement est en cours
download_active = False
log_dir = os.path.join(APP_FOLDER, "logs")
# Log directory
# Docker mode: /config/logs (persisted in config volume)
# Traditional mode: /app/RGSX/logs (current behavior)
if _is_docker_mode:
log_dir = os.path.join(CONFIG_FOLDER, "logs")
else:
log_dir = os.path.join(APP_FOLDER, "logs")
log_file = os.path.join(log_dir, "RGSX.log")
log_file_web = os.path.join(log_dir, 'rgsx_web.log')
@@ -75,9 +218,17 @@ MUSIC_FOLDER = os.path.join(APP_FOLDER, "assets", "music")
GAMELISTXML = os.path.join(ROMS_FOLDER, "ports","gamelist.xml")
GAMELISTXML_WINDOWS = os.path.join(ROMS_FOLDER, "windows","gamelist.xml")
# Dans le Dossier de sauvegarde : /saves/ports/rgsx
# Dans le Dossier de sauvegarde : /saves/ports/rgsx (traditional) or /config (Docker)
IMAGES_FOLDER = os.path.join(SAVE_FOLDER, "images")
GAMES_FOLDER = os.path.join(SAVE_FOLDER, "games")
# GAME_LISTS_FOLDER: Platform game database JSON files (extracted from games.zip)
# Always in SAVE_FOLDER/games for both Docker and Traditional modes
# These are small JSON files containing available games per platform
GAME_LISTS_FOLDER = os.path.join(SAVE_FOLDER, "games")
# Legacy alias for backwards compatibility (some code still uses GAMES_FOLDER)
GAMES_FOLDER = GAME_LISTS_FOLDER
SOURCES_FILE = os.path.join(SAVE_FOLDER, "systems_list.json")
JSON_EXTENSIONS = os.path.join(SAVE_FOLDER, "rom_extensions.json")
PRECONF_CONTROLS_PATH = os.path.join(APP_FOLDER, "assets", "controls")
@@ -106,8 +257,8 @@ OTA_data_ZIP = os.path.join(OTA_SERVER_URL, "games.zip")
#CHEMINS DES EXECUTABLES
UNRAR_EXE = os.path.join(APP_FOLDER,"assets","progs","unrar.exe")
XDVDFS_EXE = os.path.join(APP_FOLDER,"assets", "progs", "xdvdfs.exe")
XDVDFS_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "xdvdfs")
XISO_EXE = os.path.join(APP_FOLDER,"assets", "progs", "extract-xiso_win.exe")
XISO_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "extract-xiso_linux")
PS3DEC_EXE = os.path.join(APP_FOLDER,"assets", "progs", "ps3dec_win.exe")
PS3DEC_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "ps3dec_linux")
SEVEN_Z_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "7zz")

View File

@@ -1788,31 +1788,31 @@ def handle_xbox(dest_dir, iso_files, url=None):
time.sleep(2)
if config.OPERATING_SYSTEM == "Windows":
# Sur Windows; telecharger le fichier exe
XDVDFS_EXE = config.XDVDFS_EXE
xdvdfs_cmd = [XDVDFS_EXE, "pack"] # Liste avec 2 éléments
XISO_EXE = config.XISO_EXE
extract_xiso_cmd = [XISO_EXE, "-r"] # Liste avec 2 éléments
else:
# Linux/Batocera : télécharger le fichier xdvdfs
XDVDFS_LINUX = config.XDVDFS_LINUX
XISO_LINUX = config.XISO_LINUX
try:
stat_info = os.stat(XDVDFS_LINUX)
stat_info = os.stat(XISO_LINUX)
mode = stat_info.st_mode
logger.debug(f"Permissions de {XDVDFS_LINUX}: {oct(mode)}")
logger.debug(f"Permissions de {XISO_LINUX}: {oct(mode)}")
logger.debug(f"Propriétaire: {stat_info.st_uid}, Groupe: {stat_info.st_gid}")
# Vérifier si le fichier est exécutable
if not os.access(XDVDFS_LINUX, os.X_OK):
logger.error(f"Le fichier {XDVDFS_LINUX} n'est pas exécutable")
if not os.access(XISO_LINUX, os.X_OK):
logger.error(f"Le fichier {XISO_LINUX} n'est pas exécutable")
try:
os.chmod(XDVDFS_LINUX, 0o755)
logger.info(f"Permissions corrigées pour {XDVDFS_LINUX}")
os.chmod(XISO_LINUX, 0o755)
logger.info(f"Permissions corrigées pour {XISO_LINUX}")
except Exception as e:
logger.error(f"Impossible de modifier les permissions: {str(e)}")
return False, "Erreur de permissions sur xdvdfs"
except Exception as e:
logger.error(f"Erreur lors de la vérification des permissions: {str(e)}")
xdvdfs_cmd = [XDVDFS_LINUX, "pack"] # Liste avec 2 éléments
extract_xiso_cmd = [XISO_LINUX, "-r"] # Liste avec 2 éléments
try:
# Utiliser uniquement la liste fournie (nouveaux ISO extraits). Fallback scan uniquement si liste vide.
@@ -1862,16 +1862,21 @@ def handle_xbox(dest_dir, iso_files, url=None):
logger.info(f"Démarrage conversion Xbox: {total} ISO(s)")
for idx, iso_xbox_source in enumerate(iso_files, start=1):
logger.debug(f"Traitement de l'ISO Xbox: {iso_xbox_source}")
xiso_dest = os.path.splitext(iso_xbox_source)[0] + "_xbox.iso"
# Construction de la commande avec des arguments distincts
cmd = xdvdfs_cmd + [iso_xbox_source, xiso_dest]
logger.debug(f"Exécution de la commande: {' '.join(cmd)}")
# extract-xiso -r repackage l'ISO en place
# Il faut exécuter la commande depuis le dossier contenant l'ISO
iso_dir = os.path.dirname(iso_xbox_source)
iso_filename = os.path.basename(iso_xbox_source)
# Utiliser le nom de fichier relatif et définir le répertoire de travail
cmd = extract_xiso_cmd + [iso_filename]
logger.debug(f"Exécution de la commande: {' '.join(cmd)} (cwd: {iso_dir})")
process = subprocess.run(
cmd,
capture_output=True,
text=True
text=True,
cwd=iso_dir
)
if process.returncode != 0:
@@ -1883,27 +1888,34 @@ def handle_xbox(dest_dir, iso_files, url=None):
if url not in config.download_progress:
config.download_progress[url] = {}
config.download_progress[url]["status"] = "Error"
config.download_progress[url]["message"] = {process.stderr}
config.download_progress[url]["message"] = process.stderr
config.download_progress[url]["progress_percent"] = 0
config.needs_redraw = True
if isinstance(config.history, list):
for entry in config.history:
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "Downloading"):
entry["status"] = "Error"
entry["message"] = {process.stderr}
entry["message"] = process.stderr
save_history(config.history)
break
except Exception:
pass
return False, err_msg
# Vérifier que l'ISO converti a été créé
if os.path.exists(xiso_dest):
logger.info(f"ISO converti avec succès: {xiso_dest}")
# Remplacer l'ISO original par l'ISO converti
os.remove(iso_xbox_source)
os.rename(xiso_dest, iso_xbox_source)
logger.debug(f"ISO original remplacé par la version convertie")
# Vérifier que l'ISO existe toujours (extract-xiso le modifie en place)
if os.path.exists(iso_xbox_source):
logger.info(f"ISO repackagé avec succès: {iso_xbox_source}")
logger.debug(f"ISO converti au format XISO en place")
# Supprimer le fichier .old créé par extract-xiso (backup)
old_file = iso_xbox_source + ".old"
if os.path.exists(old_file):
try:
os.remove(old_file)
logger.debug(f"Fichier backup .old supprimé: {old_file}")
except Exception as e:
logger.warning(f"Impossible de supprimer le fichier .old: {e}")
# Mise à jour progression de conversion (coarse-grain)
try:
percent = int(idx / total * 100) if total > 0 else 100
@@ -1922,7 +1934,7 @@ def handle_xbox(dest_dir, iso_files, url=None):
except Exception:
pass
else:
err_msg = f"L'ISO converti n'a pas été créé: {xiso_dest}"
err_msg = f"L'ISO source a disparu après conversion: {iso_xbox_source}"
logger.error(err_msg)
try:
if url:

View File

@@ -1,3 +1,3 @@
{
"version": "2.3.1.5"
"version": "2.3.1.8"
}