Compare commits

..

10 Commits

Author SHA1 Message Date
skymike03
5fa606b3de v2.3.0.9
- Clear history now keep current downloading or extracting
- converting xbox iso if symlink activated works now
- sanitize some display status
2025-10-25 17:18:47 +02:00
skymike03
179d10facd update retrobat launch script and add python to windows folder 2025-10-24 23:34:15 +02:00
skymike03
06c06d0223 v2.3.0.8
- add RGSX_WEB service for batocera only
- add an option to enable/disable RGSX Web Service at boot (in settings menu) . With that, you can have rgsx web server enabled without launching rgsx main appp.
2025-10-24 23:17:27 +02:00
skymike03
18e5f6d637 update release name 2025-10-23 17:30:56 +02:00
skymike03
d2a52c5a2e update push new test 2025-10-22 02:35:46 +02:00
skymike03
9df7a35e85 v2.3.0.7 changes for updates and release system simplify 2025-10-22 02:26:22 +02:00
skymike03
26f1f58d7a Merge branch 'main' of https://github.com/RetroGameSets/RGSX 2025-10-22 02:14:37 +02:00
skymike03
a9cdba3aa6 v2.3.0.7
- minor updates to install script
2025-10-22 02:14:35 +02:00
RGS
1a118247d9 Update Windows installation instructions in release.yml 2025-10-22 02:11:28 +02:00
RGS
5497727e2e Update release workflow to rename Retrobat package to use full name 2025-10-22 02:09:55 +02:00
22 changed files with 550 additions and 131 deletions

View File

@@ -27,7 +27,7 @@ jobs:
echo "Building RGSX package from ports/RGSX/ directory…"
cd ports/RGSX
zip -r "../../dist/RGSX_${VERSION}.zip" . \
zip -r "../../dist/RGSX_update_latest.zip" . \
-x "logs/*" \
"logs/**" \
"images/*" \
@@ -43,15 +43,13 @@ jobs:
"*.log"
cd ../..
echo "Creating stable-named copy for latest download link…"
cp -f "dist/RGSX_${VERSION}.zip" "dist/RGSX_latest.zip"
cp -f "dist/RGSX_update_latest.zip" "dist/RGSX_latest.zip"
echo "✓ RGSX package created successfully"
echo ""
echo "Building RGSX Retrobat package (includes ports/ and windows/ directories)…"
zip -r "dist/RGSX_Retrobat_${VERSION}.zip" ports windows \
echo "Building RGSX Full package (includes ports/ and windows/ directories)…"
zip -r "dist/RGSX_full_latest.zip" ports windows \
-x "ports/RGSX/logs/*" \
"ports/RGSX/logs/**" \
"ports/RGSX/images/*" \
@@ -73,8 +71,6 @@ jobs:
"*.xml" \
"*.log"
echo "Creating stable-named copy for Retrobat package…"
cp -f "dist/RGSX_Retrobat_${VERSION}.zip" "dist/RGSX_Retrobat_latest.zip"
echo "✓ All packages created successfully"
ls -lh dist/
@@ -88,28 +84,50 @@ jobs:
draft: false
prerelease: false
body: |
## 📦 RGSX Release ${{ github.ref_name }}
# 📦 RGSX Release ${{ github.ref_name }}
### 📥 Installation
## 📥 Automatic Installation (Only for batocera Knulli)
### ON PC :
1. Open File Manager (F1) then "Applications" and launch xterm
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
3. Launch RGSX from "Ports" menu
### ON RASPBERRY/ARM SBC / HANDHELD :
1. Connect your device with SSH on a computer/smartphone connected to same network (ssh root@IPADDRESS , pass:linux)
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
3. Launch RGSX from "Ports" menu
## 📥 Manual Installation
#### Batocera/Knulli
1. Download `RGSX_${{ github.ref_name }}.zip`
2. Extract in `/userdata/roms/ports/RGSX/`
### Batocera/Knulli
1. Download latest release : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_full_latest.zip
2. Extract only PORTS folder in `/userdata/roms/`
3. Launch RGSX from the Ports menu
#### Retrobat/Windows
1. `RGSX_Retrobat_${{ github.ref_name }}.zip`
2. Extract in retrobat/roms (les dossiers `ports/` et `windows/` seront créés automatiquement)
### Retrobat/Full Installation on Windows
1. Download latest release : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_full_latest.zip
2. Extract all folders in your Retrobat\roms folder
3. Launch RGSX from system "Windows"
## 📥 Manual Update (you shouldn't need to do this as RGSX updates automatically on each start)
#### Batocera/Knulli
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
2. Extract only PORTS folder in `/userdata/roms/`
3. Launch RGSX from the Ports menu
#### Retrobat
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
2. Extract all folders in your Retrobat\roms folder
3. Launch RGSX from system "Windows"
### 📖 Documentation
[]README.md](https://github.com/${{ github.repository }}/blob/main/README.md)
[README.md](https://github.com/${{ github.repository }}/blob/main/README.md)
files: |
dist/RGSX_${{ github.ref_name }}.zip
dist/RGSX_latest.zip
dist/RGSX_Retrobat_${{ github.ref_name }}.zip
dist/RGSX_Retrobat_latest.zip
dist/RGSX_latest.zip
dist/RGSX_update_latest.zip
dist/RGSX_full_latest.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -11,7 +11,6 @@ ports/gamelist.xml
prboom/
*.log
*.rar
*.zip
.vscode/
ports/RGSX.bat
.venv/

View File

@@ -1,4 +1,4 @@
#!/bin/bash
# Supprimer SDL_VIDEODRIVER=fbcon pour laisser SDL choisir le pilote
# export SDL_VIDEODRIVER=fbcon
/usr/bin/python3 /userdata/roms/ports/RGSX
#!/usr/bin/env python3
SCRIPT_DIR=$(dirname "$(realpath "$0")")
python3 "$SCRIPT_DIR/__main__.py"

View File

@@ -712,7 +712,7 @@ async def main():
config.history.append({
"platform": platform_name,
"game_name": game_name,
"status": "downloading",
"status": "Downloading",
"progress": 0,
"url": url,
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -822,7 +822,7 @@ async def main():
config.history.append({
"platform": platform_name,
"game_name": game_name,
"status": "downloading",
"status": "Downloading",
"progress": 0,
"message": _("download_in_progress") if _ else "Download in progress",
"url": url,
@@ -868,7 +868,7 @@ async def main():
config.history.append({
"platform": platform_name,
"game_name": game_name,
"status": "downloading",
"status": "Downloading",
"progress": 0,
"message": _("download_in_progress") if _ else "Download in progress",
"url": url,
@@ -909,7 +909,7 @@ async def main():
logger.debug(f"[HISTORY_SEARCH] Searching in {len(config.history)} history entries for url={url[:50]}...")
for entry in config.history:
#logger.debug(f"[HISTORY_ENTRY] Checking: url_match={entry['url'] == url}, status={entry['status']}, game={entry.get('game_name')}")
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
#logger.debug(f"[HISTORY_MATCH] Found matching entry for {game_name}, updating status")
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
@@ -941,7 +941,7 @@ async def main():
if "http" in message:
message = message.split("https://")[0].strip()
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["status"] = "Erreur"
entry["progress"] = 0
entry["message"] = message
@@ -972,7 +972,7 @@ async def main():
success, message = data[1], data[2]
logger.debug(f"[DOWNLOAD_TASK] Download task done - success={success}, message={message}, task_id={task_id}")
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
@@ -1002,7 +1002,7 @@ async def main():
downloaded, total_size = data[1], data[2]
progress = (downloaded / total_size * 100) if total_size > 0 else 0
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["progress"] = progress
entry["status"] = "Téléchargement"
config.needs_redraw = True

View File

@@ -0,0 +1,103 @@
#!/bin/bash
# BATOCERA SERVICE
# name: RGSX Web Service for Batocera
# description: Automatic launch Web interface service for RGSX
# author: RetroGameSets / ninao.xyz
# depends: python3
# version: 1.3
SCRIPT="/userdata/roms/ports/RGSX/rgsx_web.py"
PYTHON="/usr/bin/python3"
PIDFILE="/var/run/rgsx_web.pid"
LOGFILE="/userdata/roms/ports/RGSX/LOGS/rgsx_web_service.log"
SERVICE_NAME="rgsx_web"
# Fonction utilitaire : vérifie si le service est activé dans batocera-settings
is_enabled() {
local enabled_services
enabled_services="$(/usr/bin/batocera-settings-get system.services 2>/dev/null)"
for s in $enabled_services; do
if [ "$s" = "$SERVICE_NAME" ]; then
echo "enabled"
return
fi
done
echo "disabled"
}
case "$1" in
start)
if [ ! -f "$SCRIPT" ]; then
echo "[${SERVICE_NAME}] Error: script not found at $SCRIPT"
exit 1
fi
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
echo "[${SERVICE_NAME}] Already running (PID $(cat "$PIDFILE"))"
exit 0
fi
echo "[${SERVICE_NAME}] Starting..."
mkdir -p "$(dirname "$LOGFILE")"
$PYTHON "$SCRIPT" >> "$LOGFILE" 2>&1 &
echo $! > "$PIDFILE"
echo "[${SERVICE_NAME}] Started (PID $(cat "$PIDFILE"))"
;;
stop)
if [ -f "$PIDFILE" ]; then
echo "[${SERVICE_NAME}] Stopping..."
kill $(cat "$PIDFILE") 2>/dev/null && rm -f "$PIDFILE"
echo "[${SERVICE_NAME}] Stopped"
else
pkill -f "$PYTHON $SCRIPT" && echo "[${SERVICE_NAME}] Stopped (no PID file)"
fi
;;
restart)
echo "[${SERVICE_NAME}] Restarting..."
"$0" stop
sleep 1
"$0" start
;;
status)
ENABLE_STATE=$(is_enabled)
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
echo "[${SERVICE_NAME}] Running (PID $(cat "$PIDFILE")) - ${ENABLE_STATE} on boot"
exit 0
elif pgrep -f "$PYTHON $SCRIPT" > /dev/null; then
echo "[${SERVICE_NAME}] Running (detected without PID file) - ${ENABLE_STATE} on boot"
exit 0
else
echo "[${SERVICE_NAME}] Not running - ${ENABLE_STATE} on boot"
exit 1
fi
;;
enable)
current=$(/usr/bin/batocera-settings-get system.services 2>/dev/null)
if echo "$current" | grep -qw "$SERVICE_NAME"; then
echo "[${SERVICE_NAME}] Already enabled on boot"
else
new_value="$current $SERVICE_NAME"
/usr/bin/batocera-settings-set system.services "$new_value"
echo "[${SERVICE_NAME}] Enabled on boot"
fi
;;
disable)
current=$(/usr/bin/batocera-settings-get system.services 2>/dev/null)
if echo "$current" | grep -qw "$SERVICE_NAME"; then
new_value=$(echo "$current" | sed "s/\b$SERVICE_NAME\b//g" | xargs)
/usr/bin/batocera-settings-set system.services "$new_value"
echo "[${SERVICE_NAME}] Disabled on boot"
else
echo "[${SERVICE_NAME}] Already disabled"
fi
;;
*)
echo "Usage: $0 {start|stop|restart|status|enable|disable}"
;;
esac
exit 0

View File

@@ -13,7 +13,7 @@ except Exception:
pygame = None # type: ignore
# Version actuelle de l'application
app_version = "2.3.0.6"
app_version = "2.3.0.9"
def get_application_root():
@@ -97,7 +97,7 @@ GITHUB_RELEASES_URL = f"https://github.com/{GITHUB_REPO}/releases"
# URLs pour les mises à jour OTA (Over-The-Air)
# Utilise le fichier RGSX_latest.zip qui pointe toujours vers la dernière version
OTA_UPDATE_ZIP = f"{GITHUB_RELEASES_URL}/latest/download/RGSX_latest.zip"
OTA_UPDATE_ZIP = f"{GITHUB_RELEASES_URL}/latest/download/RGSX_update_latest.zip"
OTA_VERSION_ENDPOINT = "https://retrogamesets.fr/softs/version.json" # Endpoint pour vérifier la version disponible
# URLs legacy (conservées pour compatibilité)

View File

@@ -214,10 +214,10 @@ def _launch_next_queued_download():
is_1fichier = queue_item['is_1fichier']
task_id = queue_item['task_id']
# Mettre à jour le statut dans l'historique: queued -> downloading
# Mettre à jour le statut dans l'historique: queued -> Downloading
for entry in config.history:
if entry.get('task_id') == task_id and entry.get('status') == 'queued':
entry['status'] = 'downloading'
if entry.get('task_id') == task_id and entry.get('status') == 'Queued':
entry['status'] = 'Downloading'
entry['message'] = _("download_in_progress")
save_history(config.history)
break
@@ -665,15 +665,15 @@ def handle_controls(event, sources, joystick, screen):
'is_zip_non_supported': pending_download[3],
'is_1fichier': is_1fichier_url(url),
'task_id': task_id,
'status': 'queued'
'status': 'Queued'
}
config.download_queue.append(queue_item)
# Ajouter une entrée à l'historique avec status "queued"
# Ajouter une entrée à l'historique avec status "Queued"
config.history.append({
'platform': platform,
'game_name': game_name,
'status': 'queued',
'status': 'Queued',
'url': url,
'progress': 0,
'message': _("download_queued"),
@@ -827,7 +827,7 @@ def handle_controls(event, sources, joystick, screen):
elif is_input_matched(event, "cancel") or is_input_matched(event, "history"):
if config.history and config.current_history_item < len(config.history):
entry = config.history[config.current_history_item]
if entry.get("status") in ["downloading", "Téléchargement", "Extracting"] and is_input_matched(event, "cancel"):
if entry.get("status") in ["Downloading", "Téléchargement", "Extracting"] and is_input_matched(event, "cancel"):
config.menu_state = "confirm_cancel_download"
config.confirm_cancel_selection = 0
config.needs_redraw = True
@@ -889,7 +889,7 @@ def handle_controls(event, sources, joystick, screen):
# 0 = Non, 1 = Oui
if config.confirm_clear_selection == 1: # Oui
clear_history()
config.history = []
config.history = load_history() # Recharger l'historique (conserve les téléchargements en cours)
config.current_history_item = 0
config.history_scroll_offset = 0
config.menu_state = "history"
@@ -1078,7 +1078,7 @@ def handle_controls(event, sources, joystick, screen):
url = entry.get("url")
if url:
# Mettre à jour le statut
entry["status"] = "downloading"
entry["status"] = "Downloading"
entry["progress"] = 0
entry["message"] = "Téléchargement en cours"
save_history(config.history)
@@ -1283,7 +1283,7 @@ def handle_controls(event, sources, joystick, screen):
# Mark all in-progress downloads as canceled in history
try:
for entry in getattr(config, 'history', []) or []:
if entry.get("status") in ["downloading", "Téléchargement", "Extracting"]:
if entry.get("status") in ["Downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Canceled"
entry["progress"] = 0
entry["message"] = _("download_canceled") if _ else "Download canceled"
@@ -1609,7 +1609,13 @@ def handle_controls(event, sources, joystick, screen):
# Sous-menu Settings
elif config.menu_state == "pause_settings_menu":
sel = getattr(config, 'pause_settings_selection', 0)
# Calculer le nombre total d'options selon le système
total = 4 # music, symlink, api keys, back
web_service_index = -1
if config.OPERATING_SYSTEM == "Linux":
total = 5 # music, symlink, web_service, api keys, back
web_service_index = 2
if is_input_matched(event, "up"):
config.pause_settings_selection = (sel - 1) % total
config.needs_redraw = True
@@ -1618,6 +1624,7 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
elif is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right"):
sel = getattr(config, 'pause_settings_selection', 0)
# Option 0: Music toggle
if sel == 0 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
config.music_enabled = not config.music_enabled
save_music_config()
@@ -1630,6 +1637,7 @@ def handle_controls(event, sources, joystick, screen):
pygame.mixer.music.stop()
config.needs_redraw = True
logger.info(f"Musique {'activée' if config.music_enabled else 'désactivée'} via settings")
# Option 1: Symlink toggle
elif sel == 1 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
from rgsx_settings import set_symlink_option, get_symlink_option
current_status = get_symlink_option()
@@ -1638,10 +1646,32 @@ def handle_controls(event, sources, joystick, screen):
config.popup_timer = 3000 if success else 5000
config.needs_redraw = True
logger.info(f"Symlink option {'activée' if not current_status else 'désactivée'} via settings")
elif sel == 2 and is_input_matched(event, "confirm"):
# Option 2: Web Service toggle (seulement si Linux)
elif sel == web_service_index and web_service_index >= 0 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
from utils import toggle_web_service_at_boot, check_web_service_status
current_status = check_web_service_status()
# Afficher un message de chargement
config.popup_message = _("settings_web_service_enabling") if not current_status else _("settings_web_service_disabling")
config.popup_timer = 1000
config.needs_redraw = True
# Exécuter en thread pour ne pas bloquer l'UI
import threading
def toggle_service():
success, message = toggle_web_service_at_boot(not current_status)
config.popup_message = message
config.popup_timer = 5000 if success else 7000
config.needs_redraw = True
if success:
logger.info(f"Service web {'activé' if not current_status else 'désactivé'} au démarrage")
else:
logger.error(f"Erreur toggle service web: {message}")
threading.Thread(target=toggle_service, daemon=True).start()
# Option API Keys (index varie selon Linux ou pas)
elif sel == (web_service_index + 1 if web_service_index >= 0 else 2) and is_input_matched(event, "confirm"):
config.menu_state = "pause_api_keys_status"
config.needs_redraw = True
elif sel == 3 and is_input_matched(event, "confirm"):
# Option Back (dernière option)
elif sel == (total - 1) and is_input_matched(event, "confirm"):
config.menu_state = "pause_menu"
config.last_state_change_time = pygame.time.get_ticks()
config.needs_redraw = True

View File

@@ -956,7 +956,7 @@ def draw_history_list(screen):
# Cherche une entrée en cours de téléchargement pour afficher la vitesse
speed_str = ""
for entry in history:
if entry.get("status") in ["Téléchargement", "downloading"]:
if entry.get("status") in ["Téléchargement", "Downloading"]:
speed = entry.get("speed", 0.0)
if speed and speed > 0:
speed_str = f" - {speed:.2f} Mo/s"
@@ -1001,7 +1001,7 @@ def draw_history_list(screen):
current_history_item_inverted = 0
speed = 0.0
if history and history[current_history_item_inverted].get("status") in ["Téléchargement", "downloading"]:
if history and history[current_history_item_inverted].get("status") in ["Téléchargement", "Downloading"]:
speed = history[current_history_item_inverted].get("speed", 0.0)
if speed > 0:
speed_str = f"{speed:.2f} Mo/s"
@@ -1094,7 +1094,7 @@ def draw_history_list(screen):
provider_prefix = entry.get("provider_prefix") or (entry.get("provider") + ":" if entry.get("provider") else "")
# Compute status text (optimized version without redundant prefix for errors)
if status in ["Téléchargement", "downloading"]:
if status in ["Téléchargement", "Downloading"]:
# Vérifier si un message personnalisé existe (ex: mode gratuit avec attente)
custom_message = entry.get('message', '')
# Détecter les messages du mode gratuit (commencent par '[' dans toutes les langues)
@@ -2040,6 +2040,7 @@ def draw_pause_games_menu(screen, selected_index):
def draw_pause_settings_menu(screen, selected_index):
from rgsx_settings import get_symlink_option
from utils import check_web_service_status
# Music
if config.music_enabled:
music_name = config.current_music_name or ""
@@ -2057,16 +2058,34 @@ def draw_pause_settings_menu(screen, selected_index):
if ' : ' in symlink_option:
base, val = symlink_option.split(' : ',1)
symlink_option = f"{base} : < {val.strip()} >"
# Web Service at boot (only on Linux/Batocera)
web_service_txt = ""
if config.OPERATING_SYSTEM == "Linux":
web_service_enabled = check_web_service_status()
web_service_status = _("settings_web_service_enabled") if web_service_enabled else _("settings_web_service_disabled")
web_service_txt = f"{_('settings_web_service')} : < {web_service_status} >"
api_keys_txt = _("menu_api_keys_status") if _ else "API Keys"
back_txt = _("menu_back") if _ else "Back"
options = [music_option, symlink_option, api_keys_txt, back_txt]
# Construction de la liste des options
options = [music_option, symlink_option]
if web_service_txt: # Ajouter seulement si Linux/Batocera
options.append(web_service_txt)
options.extend([api_keys_txt, back_txt])
_draw_submenu_generic(screen, _("menu_settings_category") if _ else "Settings", options, selected_index)
instruction_keys = [
"instruction_settings_music",
"instruction_settings_symlink",
]
if web_service_txt:
instruction_keys.append("instruction_settings_web_service")
instruction_keys.extend([
"instruction_settings_api_keys",
"instruction_generic_back",
]
])
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
if key:
button_height = int(config.screen_height * 0.045)
@@ -2182,6 +2201,7 @@ def draw_pause_api_keys_status(screen):
hint_rect = hint_surf.get_rect(center=(config.screen_width//2, menu_y + menu_height - 30))
screen.blit(hint_surf, hint_rect)
def draw_filter_platforms_menu(screen):
"""Affiche le menu de filtrage des plateformes (afficher/masquer)."""
from rgsx_settings import load_rgsx_settings

View File

@@ -50,12 +50,20 @@ def load_history():
logger.warning(f"Format history.json invalide (pas une liste), retour liste vide")
return []
# Filtrer les entrées valides au lieu de tout rejeter
valid_entries = []
invalid_count = 0
for entry in history:
if not all(key in entry for key in ['platform', 'game_name', 'status']):
logger.warning(f"Entrée d'historique invalide : {entry}")
return []
#logger.debug(f"Historique chargé depuis {history_path}, {len(history)} entrées")
return history
if isinstance(entry, dict) and all(key in entry for key in ['platform', 'game_name', 'status']):
valid_entries.append(entry)
else:
invalid_count += 1
logger.warning(f"Entrée d'historique invalide ignorée : {entry}")
if invalid_count > 0:
logger.info(f"Historique chargé : {len(valid_entries)} valides, {invalid_count} invalides ignorées")
#logger.debug(f"Historique chargé depuis {history_path}, {len(valid_entries)} entrées")
return valid_entries
except (FileNotFoundError, json.JSONDecodeError) as e:
logger.error(f"Erreur lors de la lecture de {history_path} : {e}")
return []
@@ -106,12 +114,26 @@ def add_to_history(platform, game_name, status, url=None, progress=0, message=No
return entry
def clear_history():
"""Vide l'historique."""
"""Vide l'historique en conservant les téléchargements en cours."""
history_path = getattr(config, 'HISTORY_PATH')
try:
# Charger l'historique actuel
current_history = load_history()
# Conserver uniquement les entrées avec statut actif (téléchargement, extraction ou conversion en cours)
# Supporter les deux variantes de statut (anglais et français)
active_statuses = {"Downloading", "Téléchargement", "downloading", "Extracting", "Converting", "Queued"}
preserved_entries = [
entry for entry in current_history
if entry.get("status") in active_statuses
]
# Sauvegarder l'historique filtré
with open(history_path, "w", encoding='utf-8') as f:
json.dump([], f)
logger.info(f"Historique vidé : {history_path}")
json.dump(preserved_entries, f, indent=2, ensure_ascii=False)
removed_count = len(current_history) - len(preserved_entries)
logger.info(f"Historique vidé : {history_path} ({removed_count} entrées supprimées, {len(preserved_entries)} conservées)")
except Exception as e:
logger.error(f"Erreur lors du vidage de {history_path} : {e}")

View File

@@ -202,6 +202,15 @@
,"instruction_settings_music": "Hintergrundmusik aktivieren oder deaktivieren"
,"instruction_settings_symlink": "Verwendung von Symlinks für Installationen umschalten"
,"instruction_settings_api_keys": "Gefundene Premium-API-Schlüssel ansehen"
,"instruction_settings_web_service": "Web-Dienst Autostart beim Booten aktivieren/deaktivieren"
,"settings_web_service": "Web-Dienst beim Booten"
,"settings_web_service_enabled": "Aktiviert"
,"settings_web_service_disabled": "Deaktiviert"
,"settings_web_service_enabling": "Web-Dienst wird aktiviert..."
,"settings_web_service_disabling": "Web-Dienst wird deaktiviert..."
,"settings_web_service_success_enabled": "Web-Dienst beim Booten aktiviert"
,"settings_web_service_success_disabled": "Web-Dienst beim Booten deaktiviert"
,"settings_web_service_error": "Fehler: {0}"
,"controls_desc_confirm": "Bestätigen (z.B. A/Kreuz)"
,"controls_desc_cancel": "Abbrechen/Zurück (z.B. B/Kreis)"
,"controls_desc_up": "UP ↑"

View File

@@ -203,6 +203,15 @@
,"instruction_settings_music": "Enable or disable background music playback"
,"instruction_settings_symlink": "Toggle using filesystem symlinks for installs"
,"instruction_settings_api_keys": "See detected premium provider API keys"
,"instruction_settings_web_service": "Enable/disable web service auto-start at boot"
,"settings_web_service": "Web Service at Boot"
,"settings_web_service_enabled": "Enabled"
,"settings_web_service_disabled": "Disabled"
,"settings_web_service_enabling": "Enabling web service..."
,"settings_web_service_disabling": "Disabling web service..."
,"settings_web_service_success_enabled": "Web service enabled at boot"
,"settings_web_service_success_disabled": "Web service disabled at boot"
,"settings_web_service_error": "Error: {0}"
,"controls_desc_confirm": "Confirm (e.g. A/Cross)"
,"controls_desc_cancel": "Cancel/Back (e.g. B/Circle)"
,"controls_desc_up": "UP ↑"

View File

@@ -202,6 +202,15 @@
,"instruction_settings_music": "Activar o desactivar música de fondo"
,"instruction_settings_symlink": "Alternar uso de symlinks en instalaciones"
,"instruction_settings_api_keys": "Ver claves API premium detectadas"
,"instruction_settings_web_service": "Activar/desactivar inicio automático del servicio web"
,"settings_web_service": "Servicio Web al Inicio"
,"settings_web_service_enabled": "Activado"
,"settings_web_service_disabled": "Desactivado"
,"settings_web_service_enabling": "Activando servicio web..."
,"settings_web_service_disabling": "Desactivando servicio web..."
,"settings_web_service_success_enabled": "Servicio web activado al inicio"
,"settings_web_service_success_disabled": "Servicio web desactivado al inicio"
,"settings_web_service_error": "Error: {0}"
,"controls_desc_confirm": "Confirmar (ej. A/Cruz)"
,"controls_desc_cancel": "Cancelar/Volver (ej. B/Círculo)"
,"controls_desc_up": "UP ↑"

View File

@@ -203,6 +203,15 @@
,"instruction_settings_music": "Activer ou désactiver la lecture musicale"
,"instruction_settings_symlink": "Basculer l'utilisation de symlinks pour l'installation"
,"instruction_settings_api_keys": "Voir les clés API détectées des services premium"
,"instruction_settings_web_service": "Activer/désactiver le démarrage automatique du service web"
,"settings_web_service": "Service Web au démarrage"
,"settings_web_service_enabled": "Activé"
,"settings_web_service_disabled": "Désactivé"
,"settings_web_service_enabling": "Activation du service web..."
,"settings_web_service_disabling": "Désactivation du service web..."
,"settings_web_service_success_enabled": "Service web activé au démarrage"
,"settings_web_service_success_disabled": "Service web désactivé au démarrage"
,"settings_web_service_error": "Erreur : {0}"
,"controls_desc_confirm": "Valider (ex: A/Croix)"
,"controls_desc_cancel": "Annuler/Retour (ex: B/Rond)"
,"controls_desc_up": "UP ↑"

View File

@@ -196,6 +196,15 @@
,"instruction_settings_music": "Abilitare o disabilitare musica di sottofondo"
,"instruction_settings_symlink": "Abilitare/disabilitare uso symlink per installazioni"
,"instruction_settings_api_keys": "Mostrare chiavi API premium rilevate"
,"instruction_settings_web_service": "Attivare/disattivare avvio automatico servizio web all'avvio"
,"settings_web_service": "Servizio Web all'Avvio"
,"settings_web_service_enabled": "Abilitato"
,"settings_web_service_disabled": "Disabilitato"
,"settings_web_service_enabling": "Abilitazione servizio web..."
,"settings_web_service_disabling": "Disabilitazione servizio web..."
,"settings_web_service_success_enabled": "Servizio web abilitato all'avvio"
,"settings_web_service_success_disabled": "Servizio web disabilitato all'avvio"
,"settings_web_service_error": "Errore: {0}"
,"controls_desc_confirm": "Confermare (es. A/Croce)"
,"controls_desc_cancel": "Annullare/Indietro (es. B/Cerchio)"
,"controls_desc_up": "UP ↑"

View File

@@ -196,6 +196,15 @@
,"instruction_settings_music": "Ativar ou desativar música de fundo"
,"instruction_settings_symlink": "Ativar/desativar uso de symlinks para instalações"
,"instruction_settings_api_keys": "Ver chaves API premium detectadas"
,"instruction_settings_web_service": "Ativar/desativar início automático do serviço web na inicialização"
,"settings_web_service": "Serviço Web na Inicialização"
,"settings_web_service_enabled": "Ativado"
,"settings_web_service_disabled": "Desativado"
,"settings_web_service_enabling": "Ativando serviço web..."
,"settings_web_service_disabling": "Desativando serviço web..."
,"settings_web_service_success_enabled": "Serviço web ativado na inicialização"
,"settings_web_service_success_disabled": "Serviço web desativado na inicialização"
,"settings_web_service_error": "Erro: {0}"
,"controls_desc_confirm": "Confirmar (ex. A/Cruz)"
,"controls_desc_cancel": "Cancelar/Voltar (ex. B/Círculo)"
,"controls_desc_up": "UP ↑"

View File

@@ -338,7 +338,7 @@ def download_1fichier_free_mode(url, dest_dir, session, log_callback=None, progr
return (True, filepath, None)
except Exception as e:
error_msg = f"Error downloading with free mode: {str(e)}"
error_msg = f"Error Downloading with free mode: {str(e)}"
_log(error_msg)
logger.error(error_msg, exc_info=True)
return (False, None, error_msg)
@@ -495,7 +495,7 @@ async def check_for_updates():
# Créer le dossier UPDATE_FOLDER s'il n'existe pas
os.makedirs(UPDATE_FOLDER, exist_ok=True)
update_zip_path = os.path.join(UPDATE_FOLDER, f"RGSX_v{latest_version}.zip")
update_zip_path = os.path.join(UPDATE_FOLDER, f"RGSX_update_v{latest_version}.zip")
logger.debug(f"Téléchargement de {UPDATE_ZIP} vers {update_zip_path}")
# Télécharger le ZIP
@@ -679,7 +679,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
def download_thread():
try:
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "downloading"
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "Downloading"
# pour que l'interface web puisse afficher le téléchargement en cours
# TOUJOURS charger l'historique existant depuis le fichier pour éviter d'écraser les anciennes entrées
@@ -690,8 +690,8 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
for entry in config.history:
if entry.get("url") == url:
entry_exists = True
# Réinitialiser le status à "downloading"
entry["status"] = "downloading"
# Réinitialiser le status à "Downloading"
entry["status"] = "Downloading"
entry["progress"] = 0
entry["downloaded_size"] = 0
entry["platform"] = platform
@@ -706,7 +706,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
"platform": platform,
"game_name": game_name,
"url": url,
"status": "downloading",
"status": "Downloading",
"progress": 0,
"downloaded_size": 0,
"total_size": 0,
@@ -1126,7 +1126,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
try:
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["status"] = "Extracting"
entry["progress"] = 0
entry["message"] = "Préparation de l'extraction..."
@@ -1212,7 +1212,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
@@ -1252,7 +1252,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
# NOTE: On ne touche PAS au timestamp qui doit rester celui de création
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["downloaded_size"] = downloaded
entry["total_size"] = total_size
entry["speed"] = speed
@@ -1284,7 +1284,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
logger.debug(f"[DRAIN_QUEUE] Processing final message: success={success}, message={message[:100] if message else 'None'}")
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
@@ -1406,7 +1406,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
link = url.split('&af=')[0]
logger.debug(f"URL nettoyée: {link}")
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "downloading"
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "Downloading"
# pour que l'interface web puisse afficher le téléchargement en cours
# Charger l'historique existant depuis le fichier
@@ -1418,8 +1418,8 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
for entry in config.history:
if entry.get("url") == url:
entry_exists = True
# Réinitialiser le status à "downloading"
entry["status"] = "downloading"
# Réinitialiser le status à "Downloading"
entry["status"] = "Downloading"
entry["progress"] = 0
entry["downloaded_size"] = 0
entry["platform"] = platform
@@ -1434,7 +1434,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
"platform": platform,
"game_name": game_name,
"url": url,
"status": "downloading",
"status": "Downloading",
"progress": 0,
"downloaded_size": 0,
"total_size": 0,
@@ -1857,7 +1857,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
with free_lock:
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
if "url" in entry and entry["url"] == url and entry["status"] == "Downloading":
entry["progress"] = int(pct) if pct else 0
entry["downloaded_size"] = downloaded
entry["total_size"] = total
@@ -1928,7 +1928,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
else:
logger.error(f"Échec téléchargement gratuit: {error_msg}")
result[0] = False
result[1] = f"Error downloading with free mode: {error_msg}"
result[1] = f"Error Downloading with free mode: {error_msg}"
return
except Exception as e:
@@ -2061,7 +2061,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
with lock:
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
if "url" in entry and entry["url"] == url and entry["status"] == "Downloading":
entry["total_size"] = total_size
config.needs_redraw = True
break
@@ -2099,7 +2099,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
with lock:
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
if "url" in entry and entry["url"] == url and entry["status"] == "Downloading":
progress_percent = int(downloaded / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
entry["progress"] = progress_percent
@@ -2226,7 +2226,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
success, message = data[1], data[2]
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
@@ -2251,7 +2251,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["progress"] = progress_percent
entry["status"] = "Téléchargement"
entry["downloaded_size"] = downloaded
@@ -2281,7 +2281,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
logger.debug(f"[1F_DRAIN_QUEUE] Processing final message: success={success}, message={message[:100] if message else 'None'}")
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message

View File

@@ -103,7 +103,7 @@ def ensure_data_present(verbose: bool = False):
headers = {"User-Agent": "Mozilla/5.0"}
# Always show progress when we're in the 'missing data' path
show = True or verbose
print("Source data not found, downloading...")
print("Source data not found, Downloading...")
print(f"Downloading data from {url}...")
try:
with requests.get(url, stream=True, headers=headers, timeout=60) as r:
@@ -394,7 +394,7 @@ async def _run_download_with_progress(url: str, platform_name: str, game_name: s
try:
if isinstance(config.history, list):
for e in config.history:
if e.get('url') == url and e.get('status') in ("downloading", "Téléchargement", "Extracting"):
if e.get('url') == url and e.get('status') in ("Downloading", "Téléchargement", "Extracting"):
downloaded = int(e.get('downloaded_size') or 0)
total = int(e.get('total_size') or 0)
speed = e.get('speed')
@@ -646,7 +646,7 @@ def cmd_download(args):
hist.append({
"platform": platform.get('platform_name') or platform.get('platform') or args.platform,
"game_name": title,
"status": "downloading",
"status": "Downloading",
"url": url,
"progress": 0,
"message": "Téléchargement en cours",

View File

@@ -69,7 +69,8 @@ def load_rgsx_settings():
},
"show_unsupported_platforms": False,
"allow_unknown_extensions": False,
"roms_folder": ""
"roms_folder": "",
"web_service_at_boot": False
}
try:

View File

@@ -347,8 +347,8 @@ class RGSXHandler(BaseHTTPRequestHandler):
print(f"\n[DEBUG PROGRESS] history.json chargé avec {len(history)} entrées totales")
# Filtrer les entrées avec status "downloading", "Téléchargement", "Connecting", "Try X/Y"
in_progress_statuses = ["downloading", "Téléchargement", "Downloading", "Connecting", "Extracting"]
# Filtrer les entrées avec status "Downloading", "Téléchargement", "Connecting", "Try X/Y"
in_progress_statuses = ["Downloading", "Téléchargement", "Downloading", "Connecting", "Extracting"]
downloads = {}
for entry in history:
@@ -393,7 +393,7 @@ class RGSXHandler(BaseHTTPRequestHandler):
# Inclure: statuts terminés + en queue + en cours
included_statuses = [
"Download_OK", "Erreur", "error", "Canceled", "Already_Present", # Terminés
"queued", "downloading", "Téléchargement", "Downloading", "Connecting", "Extracting", # En cours
"Queued", "Downloading", "Téléchargement", "Downloading", "Connecting", "Extracting", # En cours
]
# Inclure aussi les statuts "Try X/Y" (tentatives)
visible_history = [
@@ -608,15 +608,15 @@ class RGSXHandler(BaseHTTPRequestHandler):
config.download_active = True
# Mettre à jour l'historique: queued -> downloading
# Mettre à jour l'historique: queued -> Downloading
from history import load_history, save_history
config.history = load_history()
for entry in config.history:
if entry.get('task_id') == task_id and entry.get('status') == 'queued':
entry['status'] = 'downloading'
if entry.get('task_id') == task_id and entry.get('status') == 'Queued':
entry['status'] = 'Downloading'
entry['message'] = get_translation('download_in_progress')
save_history(config.history)
logger.info(f"📋 Statut mis à jour de 'queued' à 'downloading' pour {game_name} (task_id={task_id})")
logger.info(f"📋 Statut mis à jour de 'queued' à 'Downloading' pour {game_name} (task_id={task_id})")
break
if is_1fichier:
@@ -779,7 +779,7 @@ class RGSXHandler(BaseHTTPRequestHandler):
'is_zip_non_supported': is_zip_non_supported,
'is_1fichier': is_1fichier,
'task_id': task_id,
'status': 'queued'
'status': 'Queued'
}
config.download_queue.append(queue_item)
@@ -788,7 +788,7 @@ class RGSXHandler(BaseHTTPRequestHandler):
queue_history_entry = {
'platform': platform,
'game_name': game_name,
'status': 'queued',
'status': 'Queued',
'url': game_url,
'progress': 0,
'message': get_translation('download_queued'),
@@ -819,13 +819,13 @@ class RGSXHandler(BaseHTTPRequestHandler):
config.download_active = True
logger.info(f"🚀 Lancement du premier élément de la queue: {game_name}")
# Ajouter une entrée à l'historique avec status "downloading"
# Ajouter une entrée à l'historique avec status "Downloading"
# (pas "queued" car on lance immédiatement)
import datetime
download_history_entry = {
'platform': platform,
'game_name': game_name,
'status': 'downloading',
'status': 'Downloading',
'url': game_url,
'progress': 0,
'message': get_translation('download_in_progress'),
@@ -894,7 +894,7 @@ class RGSXHandler(BaseHTTPRequestHandler):
task_id = None
for entry in history:
if entry.get('url') == url and entry.get('status') in ['downloading', 'Téléchargement', 'Downloading', 'Connecting']:
if entry.get('url') == url and entry.get('status') in ['Downloading', 'Téléchargement', 'Downloading', 'Connecting']:
# Mettre à jour le statut dans l'historique
entry['status'] = 'Canceled'
entry['progress'] = 0
@@ -2704,7 +2704,7 @@ DO NOT share this file publicly as it may contain sensitive information.
queue.forEach((item, idx) => {
const gameName = item.game_name || 'Unknown';
const platform = item.platform || 'N/A';
const status = item.status || 'queued';
const status = item.status || 'Queued';
html += `
<div class="info-item" style="display: flex; justify-content: space-between; align-items: center;">
<div style="flex: 1;">
@@ -2814,8 +2814,8 @@ DO NOT share this file publicly as it may contain sensitive information.
const isError = status === 'Erreur' || status === 'error';
const isCanceled = status === 'Canceled';
const isAlreadyPresent = status === 'Already_Present';
const isQueued = status === 'queued';
const isDownloading = status === 'downloading' || status === 'Téléchargement' || status === 'Downloading' ||
const isQueued = status === 'Queued';
const isDownloading = status === 'Downloading' || status === 'Téléchargement' || status === 'Downloading' ||
status === 'Connecting' || status === 'Extracting' || status.startsWith('Try ');
const isSuccess = status === 'Download_OK' || status === 'Completed';

View File

@@ -16,7 +16,7 @@ except Exception:
pygame = None # type: ignore
import glob
import threading
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
from rgsx_settings import load_rgsx_settings, save_rgsx_settings, get_allow_unknown_extensions
import zipfile
import time
import random
@@ -25,6 +25,7 @@ from history import save_history
from language import _
from datetime import datetime
import sys
import tempfile
logger = logging.getLogger(__name__)
@@ -85,9 +86,7 @@ def generate_support_zip():
Returns:
tuple: (success: bool, message: str, zip_path: str ou None)
"""
import zipfile
import tempfile
from datetime import datetime
try:
# Créer un fichier ZIP temporaire
@@ -162,6 +161,146 @@ DO NOT share this file publicly as it may contain sensitive information.
logger.error(f"Erreur lors de la génération du fichier de support: {e}")
return (False, str(e), None)
def toggle_web_service_at_boot(enable: bool):
"""Active ou désactive le service web au démarrage de Batocera.
Args:
enable: True pour activer, False pour désactiver
Returns:
tuple: (success: bool, message: str)
"""
try:
# Vérifier si on est sur un système compatible (Linux avec batocera-services)
if config.OPERATING_SYSTEM != "Linux":
return (False, "Web service auto-start is only available on Batocera/Linux systems")
services_dir = "/userdata/system/services"
service_file = os.path.join(services_dir, "rgsx_web")
source_file = os.path.join(config.APP_FOLDER, "assets", "progs", "rgsx_web")
if enable:
# Mode ENABLE
logger.debug("Activation du service web au démarrage...")
# 1. Créer le dossier services s'il n'existe pas
try:
os.makedirs(services_dir, exist_ok=True)
logger.debug(f"Dossier services vérifié/créé: {services_dir}")
except Exception as e:
error_msg = f"Failed to create services directory: {str(e)}"
logger.error(error_msg)
return (False, error_msg)
# 2. Copier le fichier rgsx_web
try:
if not os.path.exists(source_file):
error_msg = f"Source service file not found: {source_file}"
logger.error(error_msg)
return (False, error_msg)
shutil.copy2(source_file, service_file)
os.chmod(service_file, 0o755) # Rendre exécutable
logger.debug(f"Fichier service copié et rendu exécutable: {service_file}")
except Exception as e:
error_msg = f"Failed to copy service file: {str(e)}"
logger.error(error_msg)
return (False, error_msg)
# 3. Activer le service avec batocera-services
try:
result = subprocess.run(
['batocera-services', 'enable', 'rgsx_web'],
capture_output=True,
text=True,
timeout=10
)
if result.returncode != 0:
error_msg = f"batocera-services enable failed: {result.stderr}"
logger.error(error_msg)
return (False, error_msg)
logger.debug(f"Service activé: {result.stdout}")
except FileNotFoundError:
error_msg = "batocera-services command not found"
logger.error(error_msg)
return (False, error_msg)
except Exception as e:
error_msg = f"Failed to enable service: {str(e)}"
logger.error(error_msg)
return (False, error_msg)
# 4. Démarrer le service immédiatement
try:
result = subprocess.run(
['batocera-services', 'start', 'rgsx_web'],
capture_output=True,
text=True,
timeout=10
)
if result.returncode != 0:
# Le service peut ne pas démarrer si déjà en cours, ce n'est pas grave
logger.warning(f"batocera-services start warning: {result.stderr}")
else:
logger.debug(f"Service démarré: {result.stdout}")
except Exception as e:
logger.warning(f"Failed to start service (non-critical): {str(e)}")
success_msg = _("settings_web_service_success_enabled") if _ else "Web service enabled at boot"
logger.info(success_msg)
# Sauvegarder l'état dans rgsx_settings.json
settings = load_rgsx_settings()
settings["web_service_at_boot"] = True
save_rgsx_settings(settings)
return (True, success_msg)
else:
# Mode DISABLE
logger.debug("Désactivation du service web au démarrage...")
# 1. Désactiver le service avec batocera-services
try:
result = subprocess.run(
['batocera-services', 'disable', 'rgsx_web'],
capture_output=True,
text=True,
timeout=10
)
if result.returncode != 0:
error_msg = f"batocera-services disable failed: {result.stderr}"
logger.error(error_msg)
return (False, error_msg)
logger.debug(f"Service désactivé: {result.stdout}")
except FileNotFoundError:
error_msg = "batocera-services command not found"
logger.error(error_msg)
return (False, error_msg)
except Exception as e:
error_msg = f"Failed to disable service: {str(e)}"
logger.error(error_msg)
return (False, error_msg)
success_msg = _("settings_web_service_success_disabled") if _ else "✓ Web service disabled at boot"
logger.info(success_msg)
# Sauvegarder l'état dans rgsx_settings.json
settings = load_rgsx_settings()
settings["web_service_at_boot"] = False
save_rgsx_settings(settings)
return (True, success_msg)
except Exception as e:
error_msg = f"Unexpected error: {str(e)}"
logger.exception(error_msg)
return (False, error_msg)
_extensions_cache = None # type: ignore
_extensions_json_regenerated = False
@@ -385,7 +524,6 @@ def check_extension_before_download(url, platform, game_name):
# Autoriser si l'utilisateur a choisi d'autoriser les extensions inconnues
allow_unknown = False
try:
from rgsx_settings import get_allow_unknown_extensions
allow_unknown = get_allow_unknown_extensions()
except Exception:
allow_unknown = False
@@ -849,7 +987,7 @@ def _update_extraction_progress(url, extracted_size, total_size, lock, last_save
with lock:
if isinstance(config.history, list):
for entry in config.history:
if "status" in entry and entry["status"] in ["Téléchargement", "Extracting", "downloading"]:
if "status" in entry and entry["status"] in ["Téléchargement", "Extracting", "Downloading"]:
if "url" in entry and entry["url"] == url:
progress_percent = int(extracted_size / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
@@ -907,8 +1045,12 @@ def _handle_special_platforms(dest_dir, archive_path, before_dirs, iso_before=No
before_items: Set de tous les éléments (fichiers+dossiers) avant extraction (pour DOS)
"""
# Xbox: conversion ISO
xbox_dir = os.path.join(config.ROMS_FOLDER, "xbox")
if dest_dir == xbox_dir and iso_before is not None:
# Gérer les deux cas: symlink activé (xbox/xbox) ou désactivé (xbox)
xbox_dir_normal = os.path.join(config.ROMS_FOLDER, "xbox")
xbox_dir_symlink = os.path.join(config.ROMS_FOLDER, "xbox", "xbox")
is_xbox = (dest_dir == xbox_dir_normal or dest_dir == xbox_dir_symlink)
if is_xbox and iso_before is not None:
iso_after = set()
for root, dirs, files in os.walk(dest_dir):
for file in files:
@@ -1052,7 +1194,6 @@ def extract_zip(zip_path, dest_dir, url):
if os.path.isdir(file_path):
logger.warning(f"Conflit: dossier existant avec le même nom que le fichier {file_path}, suppression du dossier")
try:
import shutil
shutil.rmtree(file_path)
except Exception as rm_err:
logger.error(f"Impossible de supprimer le dossier {file_path}: {rm_err}")
@@ -1617,7 +1758,7 @@ def handle_xbox(dest_dir, iso_files, url=None):
# Historique
if isinstance(config.history, list):
for entry in config.history:
if entry.get("url") == url and entry.get("status") in ["Extracting", "Téléchargement", "downloading"]:
if entry.get("url") == url and entry.get("status") in ["Extracting", "Téléchargement", "Downloading"]:
entry["status"] = "Converting"
entry["progress"] = 0
entry["message"] = "Xbox conversion in progress"
@@ -1650,14 +1791,14 @@ 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"] = err_msg
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"):
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "Downloading"):
entry["status"] = "Error"
entry["message"] = err_msg
entry["message"] = {process.stderr}
save_history(config.history)
break
except Exception:
@@ -1701,7 +1842,7 @@ def handle_xbox(dest_dir, iso_files, url=None):
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"):
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "Downloading"):
entry["status"] = "Error"
entry["message"] = err_msg
save_history(config.history)
@@ -1710,6 +1851,25 @@ def handle_xbox(dest_dir, iso_files, url=None):
pass
return False, "Échec de la conversion de l'ISO"
# Conversion terminée avec succès - mettre à jour le statut final
try:
if url:
if url not in config.download_progress:
config.download_progress[url] = {}
config.download_progress[url]["status"] = "Download_OK"
config.download_progress[url]["progress_percent"] = 100
config.needs_redraw = True
if isinstance(config.history, list):
for entry in config.history:
if entry.get("url") == url and entry.get("status") == "Converting":
entry["status"] = "Download_OK"
entry["progress"] = 100
entry["message"] = "Xbox conversion completed successfully"
save_history(config.history)
break
except Exception as e:
logger.debug(f"MAJ statut final conversion ignorée: {e}")
return True, "Conversion Xbox terminée avec succès"
except Exception as e:
@@ -1890,6 +2050,26 @@ def save_music_config():
except Exception as e:
logger.error(f"Erreur lors de la sauvegarde de la configuration musique: {str(e)}")
def check_web_service_status():
"""Vérifie si le service web est activé au démarrage.
Returns:
bool: True si activé, False sinon
"""
try:
if config.OPERATING_SYSTEM != "Linux":
return False
# Lire l'état depuis rgsx_settings.json
settings = load_rgsx_settings()
return settings.get("web_service_at_boot", False)
except Exception as e:
logger.debug(f"Failed to check web service status: {e}")
return False
def normalize_platform_name(platform):
"""Normalise un nom de plateforme en supprimant espaces et convertissant en minuscules."""

View File

@@ -55,8 +55,8 @@ echo UPDATE_GAMELIST_SCRIPT : !UPDATE_GAMELIST_SCRIPT! >> "%LOG_FILE%"
echo Checking python.exe...
echo [%DATE% %TIME%] Checking python.exe at !PYTHON_EXE_FULL! >> "%LOG_FILE%"
if not exist "!PYTHON_EXE_FULL!" (
echo python.exe not found. Preparing download...
echo [%DATE% %TIME%] python.exe not found. Preparing download... >> "%LOG_FILE%"
echo python.exe not found in system/tools. Preparing to extract..
echo [%DATE% %TIME%] python.exe not found in system/tools. Preparing to extract.. >> "%LOG_FILE%"
:: Créer le dossier Python s'il n'existe pas
set "TOOLS_FOLDER_FULL=!ROOT_DIR!\system\tools"
@@ -67,29 +67,21 @@ if not exist "!PYTHON_EXE_FULL!" (
mkdir "!TOOLS_FOLDER_FULL!\Python"
)
set ZIP_URL=https://retrogamesets.fr/softs/python.zip
set "ZIP_FILE=!TOOLS_FOLDER_FULL!\python.zip"
echo ZIP_URL : !ZIP_URL!
echo [%DATE% %TIME%] ZIP_URL : !ZIP_URL! >> "%LOG_FILE%"
echo ZIP_FILE : !ZIP_FILE!
set "ZIP_FILE=%ROOT_DIR%\roms\windows\python.zip"
echo Extracting ZIP_FILE : !ZIP_FILE! in /system/tools/Python
echo [%DATE% %TIME%] ZIP_FILE : !ZIP_FILE! >> "%LOG_FILE%"
echo Downloading python.zip...
echo [%DATE% %TIME%] Downloading python.zip from !ZIP_URL!... >> "%LOG_FILE%"
curl -L "!ZIP_URL!" -o "!ZIP_FILE!"
if exist "!ZIP_FILE!" (
echo Download complete. Extracting python.zip...
echo [%DATE% %TIME%] Download complete. Extracting python.zip to !TOOLS_FOLDER_FULL!... >> "%LOG_FILE%"
echo [%DATE% %TIME%] Extracting python.zip to !TOOLS_FOLDER_FULL!... >> "%LOG_FILE%"
tar -xf "!ZIP_FILE!" -C "!TOOLS_FOLDER_FULL!\Python" --strip-components=0
echo Extraction finished.
echo [%DATE% %TIME%] Extraction finished. >> "%LOG_FILE%"
del /q "!ZIP_FILE!"
del /s /q "!ZIP_FILE!"
echo python.zip file deleted.
echo [%DATE% %TIME%] python.zip file deleted. >> "%LOG_FILE%"
) else (
echo Error: Failed to download python.zip.
echo [%DATE% %TIME%] Error: Failed to download python.zip. >> "%LOG_FILE%"
echo Error: Error python.zip not found please download it from github and put in /roms/windows folder.
echo [%DATE% %TIME%] Error: Error python.zip not found please download it from github and put in /roms/windows folder >> "%LOG_FILE%"
goto :error
)

BIN
windows/python.zip Normal file

Binary file not shown.