mirror of
https://github.com/RetroGameSets/RGSX.git
synced 2026-03-19 16:26:00 +01:00
Compare commits
14 Commits
v2.3.0.7.1
...
v2.3.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af42c31476 | ||
|
|
eeea4763f5 | ||
|
|
201d56fff4 | ||
|
|
486c9d0244 | ||
|
|
aed7da8b51 | ||
|
|
93fc4a023d | ||
|
|
b4398b1d82 | ||
|
|
751800026c | ||
|
|
8427ba60eb | ||
|
|
7fbf936af6 | ||
|
|
5fa606b3de | ||
|
|
179d10facd | ||
|
|
06c06d0223 | ||
|
|
18e5f6d637 |
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
"*.log"
|
||||
|
||||
cd ../..
|
||||
|
||||
cp -f "dist/RGSX_update_latest.zip" "dist/RGSX_latest.zip"
|
||||
|
||||
echo "✓ RGSX package created successfully"
|
||||
|
||||
@@ -84,23 +84,48 @@ jobs:
|
||||
draft: false
|
||||
prerelease: false
|
||||
body: |
|
||||
## 📦 RGSX Release ${{ github.ref_name }}
|
||||
# 📦 RGSX Release ${{ github.ref_name }}
|
||||
|
||||
### 📥 Manual 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_full_latest.zip`
|
||||
### 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/Full Installation on Windows
|
||||
1. Download `RGSX_full_latest.zip`
|
||||
### 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_update_latest.zip
|
||||
dist/RGSX_full_latest.zip
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,7 +11,6 @@ ports/gamelist.xml
|
||||
prboom/
|
||||
*.log
|
||||
*.rar
|
||||
*.zip
|
||||
.vscode/
|
||||
ports/RGSX.bat
|
||||
.venv/
|
||||
|
||||
@@ -541,7 +541,6 @@ async def main():
|
||||
config.needs_redraw = True
|
||||
logger.info("Scraping terminé")
|
||||
|
||||
import threading
|
||||
thread = threading.Thread(target=scrape_async, daemon=True)
|
||||
thread.start()
|
||||
|
||||
@@ -712,7 +711,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 +821,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 +867,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 +908,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 +940,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 +971,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 +1001,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
|
||||
|
||||
103
ports/RGSX/assets/progs/rgsx_web
Normal file
103
ports/RGSX/assets/progs/rgsx_web
Normal 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
|
||||
@@ -13,7 +13,7 @@ except Exception:
|
||||
pygame = None # type: ignore
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "2.3.0.7.1"
|
||||
app_version = "2.3.1.4"
|
||||
|
||||
|
||||
def get_application_root():
|
||||
@@ -98,7 +98,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_update_latest.zip"
|
||||
OTA_VERSION_ENDPOINT = "https://retrogamesets.fr/softs/version.json" # Endpoint pour vérifier la version disponible
|
||||
OTA_VERSION_ENDPOINT = "https://raw.githubusercontent.com/RetroGameSets/RGSX/refs/heads/main/version.json" # Endpoint pour vérifier la version disponible
|
||||
|
||||
# URLs legacy (conservées pour compatibilité)
|
||||
OTA_SERVER_URL = "https://retrogamesets.fr/softs/"
|
||||
|
||||
@@ -6,20 +6,29 @@ import re
|
||||
import os
|
||||
import datetime
|
||||
import threading
|
||||
import logging
|
||||
import config
|
||||
from config import REPEAT_DELAY, REPEAT_INTERVAL, REPEAT_ACTION_DEBOUNCE
|
||||
from config import CONTROLS_CONFIG_PATH
|
||||
from config import REPEAT_DELAY, REPEAT_INTERVAL, REPEAT_ACTION_DEBOUNCE, CONTROLS_CONFIG_PATH
|
||||
from display import draw_validation_transition, show_toast
|
||||
from network import download_rom, download_from_1fichier, is_1fichier_url, request_cancel
|
||||
from utils import (
|
||||
load_games, check_extension_before_download, is_extension_supported,
|
||||
load_extensions_json, play_random_music, sanitize_filename,
|
||||
save_music_config, load_api_keys, _get_dest_folder_name,
|
||||
extract_zip, extract_rar, find_file_with_or_without_extension
|
||||
extract_zip, extract_rar, find_file_with_or_without_extension, toggle_web_service_at_boot, check_web_service_status,
|
||||
restart_application, generate_support_zip, load_sources,
|
||||
ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string
|
||||
)
|
||||
from history import load_history, clear_history, add_to_history, save_history
|
||||
import logging
|
||||
from language import _ # Import de la fonction de traduction
|
||||
from rgsx_settings import (
|
||||
get_allow_unknown_extensions, set_display_grid, get_font_family, set_font_family,
|
||||
get_show_unsupported_platforms, set_show_unsupported_platforms,
|
||||
set_allow_unknown_extensions, get_hide_premium_systems, set_hide_premium_systems,
|
||||
get_sources_mode, set_sources_mode, set_symlink_option, get_symlink_option
|
||||
)
|
||||
from accessibility import save_accessibility_settings
|
||||
from scraper import get_game_metadata, download_image_to_surface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -214,10 +223,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
|
||||
@@ -640,12 +649,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
load_extensions_json()
|
||||
)
|
||||
zip_ok = bool(pending_download[3])
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
|
||||
# Si extension non supportée ET pas en archive connu, afficher avertissement
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
@@ -665,15 +669,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"),
|
||||
@@ -724,7 +728,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
if config.pending_download and len(config.pending_download) == 4:
|
||||
url, platform, game_name, is_zip_non_supported = config.pending_download
|
||||
if is_1fichier_url(url):
|
||||
from utils import ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
|
||||
@@ -827,7 +830,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 +892,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"
|
||||
@@ -1020,7 +1023,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Lancer la recherche des métadonnées dans un thread séparé
|
||||
|
||||
def scrape_async():
|
||||
from scraper import get_game_metadata, download_image_to_surface
|
||||
logger.info(f"Scraping métadonnées pour {game_name} sur {platform}")
|
||||
metadata = get_game_metadata(game_name, platform)
|
||||
|
||||
@@ -1078,7 +1080,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)
|
||||
@@ -1209,7 +1211,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
entry = config.history[config.current_history_item]
|
||||
platform = entry.get("platform", "")
|
||||
|
||||
# threading est déjà importé en haut du fichier (ligne 8)
|
||||
|
||||
# Utiliser le chemin réel trouvé (avec ou sans extension)
|
||||
file_path = getattr(config, 'history_actual_path', None)
|
||||
@@ -1283,7 +1284,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"
|
||||
@@ -1355,10 +1356,8 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 5: # Restart
|
||||
from utils import restart_application
|
||||
restart_application(2000)
|
||||
elif config.selected_option == 6: # Support
|
||||
from utils import generate_support_zip
|
||||
success, message, zip_path = generate_support_zip()
|
||||
if success:
|
||||
config.support_zip_path = zip_path
|
||||
@@ -1435,7 +1434,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
idx = (idx + 1) % len(layouts) if is_input_matched(event, "right") else (idx - 1) % len(layouts)
|
||||
new_cols, new_rows = layouts[idx]
|
||||
try:
|
||||
from rgsx_settings import set_display_grid
|
||||
set_display_grid(new_cols, new_rows)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur set_display_grid: {e}")
|
||||
@@ -1443,7 +1441,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.GRID_ROWS = new_rows
|
||||
# Redémarrage automatique
|
||||
try:
|
||||
from utils import restart_application
|
||||
config.menu_state = "restart_popup"
|
||||
config.popup_message = _("popup_restarting") if _ else "Restarting..."
|
||||
config.popup_timer = 2000
|
||||
@@ -1453,7 +1450,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
# 1 font size
|
||||
elif sel == 1 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
from accessibility import save_accessibility_settings
|
||||
opts = getattr(config, 'font_scale_options', [0.75,1.0,1.25,1.5,1.75])
|
||||
idx = getattr(config, 'current_font_scale_index', 1)
|
||||
idx = max(0, idx-1) if is_input_matched(event, "left") else min(len(opts)-1, idx+1)
|
||||
@@ -1473,7 +1469,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 2 font family cycle
|
||||
elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||
try:
|
||||
from rgsx_settings import get_font_family, set_font_family
|
||||
families = getattr(config, 'FONT_FAMILIES', ["pixel"]) or ["pixel"]
|
||||
current = get_font_family()
|
||||
try:
|
||||
@@ -1508,10 +1503,8 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 3 unsupported toggle
|
||||
elif sel == 3 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||
try:
|
||||
from rgsx_settings import get_show_unsupported_platforms, set_show_unsupported_platforms
|
||||
current = get_show_unsupported_platforms()
|
||||
new_val = set_show_unsupported_platforms(not current)
|
||||
from utils import load_sources
|
||||
load_sources()
|
||||
config.popup_message = _("menu_show_unsupported_enabled") if new_val else _("menu_show_unsupported_disabled")
|
||||
config.popup_timer = 3000
|
||||
@@ -1521,7 +1514,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 4 allow unknown extensions
|
||||
elif sel == 4 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions, set_allow_unknown_extensions
|
||||
current = get_allow_unknown_extensions()
|
||||
new_val = set_allow_unknown_extensions(not current)
|
||||
config.popup_message = _("menu_allow_unknown_ext_enabled") if new_val else _("menu_allow_unknown_ext_disabled")
|
||||
@@ -1532,7 +1524,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 5 hide premium systems
|
||||
elif sel == 5 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
try:
|
||||
from rgsx_settings import get_hide_premium_systems, set_hide_premium_systems
|
||||
cur = get_hide_premium_systems()
|
||||
new_val = set_hide_premium_systems(not cur)
|
||||
config.popup_message = ("Premium hidden" if new_val else "Premium visible") if _ is None else (_("popup_hide_premium_on") if new_val else _("popup_hide_premium_off"))
|
||||
@@ -1578,7 +1569,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
elif sel == 1 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
try:
|
||||
from rgsx_settings import get_sources_mode, set_sources_mode
|
||||
current_mode = get_sources_mode()
|
||||
new_mode = set_sources_mode('custom' if current_mode == 'rgsx' else 'rgsx')
|
||||
config.sources_mode = new_mode
|
||||
@@ -1609,7 +1599,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 +1614,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,18 +1627,39 @@ 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()
|
||||
success, message = set_symlink_option(not current_status)
|
||||
config.popup_message = message
|
||||
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")):
|
||||
|
||||
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
|
||||
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
|
||||
@@ -1684,7 +1702,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
idx = (idx - 1) % len(layouts) if is_input_matched(event, "left") else (idx + 1) % len(layouts)
|
||||
new_cols, new_rows = layouts[idx]
|
||||
try:
|
||||
from rgsx_settings import set_display_grid
|
||||
set_display_grid(new_cols, new_rows)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur set_display_grid: {e}")
|
||||
@@ -1693,7 +1710,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
# Redémarrage automatique pour appliquer proprement la modification de layout
|
||||
try:
|
||||
from utils import restart_application
|
||||
# Montrer brièvement l'info puis redémarrer
|
||||
config.menu_state = "restart_popup"
|
||||
config.popup_message = _("popup_restarting")
|
||||
@@ -1703,7 +1719,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
logger.error(f"Erreur lors du redémarrage après changement de layout: {e}")
|
||||
# 1: font size adjust
|
||||
elif sel == 1 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
from accessibility import save_accessibility_settings
|
||||
opts = getattr(config, 'font_scale_options', [0.75, 1.0, 1.25, 1.5, 1.75])
|
||||
idx = getattr(config, 'current_font_scale_index', 1)
|
||||
idx = max(0, idx - 1) if is_input_matched(event, "left") else min(len(opts)-1, idx + 1)
|
||||
@@ -1723,10 +1738,8 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 2: toggle unsupported
|
||||
elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||
try:
|
||||
from rgsx_settings import get_show_unsupported_platforms, set_show_unsupported_platforms
|
||||
current = get_show_unsupported_platforms()
|
||||
new_val = set_show_unsupported_platforms(not current)
|
||||
from utils import load_sources
|
||||
load_sources()
|
||||
config.popup_message = _("menu_show_unsupported_enabled") if new_val else _("menu_show_unsupported_disabled")
|
||||
config.popup_timer = 3000
|
||||
@@ -1736,7 +1749,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 3: toggle allow unknown extensions
|
||||
elif sel == 3 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions, set_allow_unknown_extensions
|
||||
current = get_allow_unknown_extensions()
|
||||
new_val = set_allow_unknown_extensions(not current)
|
||||
config.popup_message = _("menu_allow_unknown_ext_enabled") if new_val else _("menu_allow_unknown_ext_disabled")
|
||||
@@ -1794,7 +1806,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
logger.debug("Passage à restart_popup")
|
||||
# Redémarrage automatique
|
||||
from utils import restart_application
|
||||
restart_application(2000)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la suppression du fichier sources.json ou dossiers: {e}")
|
||||
@@ -1809,7 +1820,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.popup_timer = 2000
|
||||
config.needs_redraw = True
|
||||
logger.debug("Passage à restart_popup")
|
||||
from utils import restart_application
|
||||
restart_application(2000)
|
||||
else: # Non
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
@@ -1833,8 +1843,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Sélecteur de langue
|
||||
elif config.menu_state == "language_select":
|
||||
# Gestion directe des événements pour le sélecteur de langue
|
||||
from language import get_available_languages, set_language, _
|
||||
|
||||
available_languages = get_available_languages()
|
||||
|
||||
if not available_languages:
|
||||
@@ -1913,8 +1921,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
btn_idx = config.selected_filter_index - total_items
|
||||
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
|
||||
from utils import load_sources
|
||||
settings = load_rgsx_settings()
|
||||
if btn_idx == 0: # all visible
|
||||
config.filter_platforms_selection = [(n, False) for n, _ in config.filter_platforms_selection]
|
||||
@@ -1996,7 +2002,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
|
||||
# Vérifier d'abord l'extension avant d'ajouter à l'historique
|
||||
if is_1fichier_url(url):
|
||||
from utils import ensure_download_provider_keys, missing_all_provider_keys
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
@@ -2011,12 +2016,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
load_extensions_json()
|
||||
)
|
||||
zip_ok = bool(config.pending_download[3])
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
@@ -2047,12 +2047,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
extensions_data
|
||||
)
|
||||
zip_ok = bool(config.pending_download[3])
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
@@ -2101,7 +2096,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
|
||||
# Vérifier d'abord l'extension avant d'ajouter à l'historique
|
||||
if is_1fichier_url(url):
|
||||
from utils import ensure_download_provider_keys, missing_all_provider_keys
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
@@ -2116,12 +2110,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
load_extensions_json()
|
||||
)
|
||||
zip_ok = bool(config.pending_download[3])
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
@@ -2152,12 +2141,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
extensions_data
|
||||
)
|
||||
zip_ok = bool(config.pending_download[3])
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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 ↑"
|
||||
|
||||
@@ -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 ↑"
|
||||
|
||||
@@ -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 ↑"
|
||||
|
||||
@@ -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 ↑"
|
||||
|
||||
@@ -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 ↑"
|
||||
|
||||
@@ -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 ↑"
|
||||
|
||||
@@ -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)
|
||||
@@ -455,10 +455,15 @@ async def check_for_updates():
|
||||
|
||||
response = requests.get(OTA_VERSION_ENDPOINT, timeout=5)
|
||||
response.raise_for_status()
|
||||
if response.headers.get("content-type") != "application/json":
|
||||
|
||||
# Accepter différents content-types (application/json, text/plain, text/html)
|
||||
content_type = response.headers.get("content-type", "")
|
||||
allowed_types = ["application/json", "text/plain", "text/html"]
|
||||
if not any(allowed in content_type for allowed in allowed_types):
|
||||
raise ValueError(
|
||||
f"Le fichier version.json n'est pas un JSON valide (type de contenu : {response.headers.get('content-type')})"
|
||||
f"Le fichier version.json n'est pas un JSON valide (type de contenu : {content_type})"
|
||||
)
|
||||
|
||||
version_data = response.json()
|
||||
latest_version = version_data.get("version")
|
||||
logger.debug(f"Version distante : {latest_version}, version locale : {config.app_version}")
|
||||
@@ -679,7 +684,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 +695,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 +711,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 +1131,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 +1217,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 +1257,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 +1289,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 +1411,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 +1423,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 +1439,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 +1862,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 +1933,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 +2066,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 +2104,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 +2231,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 +2256,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 +2286,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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -175,6 +175,17 @@ logger.info("Chargement initial des données...")
|
||||
try:
|
||||
load_sources() # Initialise config.games_count
|
||||
logger.info(f"{len(getattr(config, 'platforms', []))} plateformes chargées")
|
||||
|
||||
# Initialiser filter_platforms_selection depuis les settings (pour filtrer les plateformes)
|
||||
from rgsx_settings import load_rgsx_settings
|
||||
settings = load_rgsx_settings()
|
||||
hidden = set(settings.get("hidden_platforms", [])) if isinstance(settings, dict) else set()
|
||||
|
||||
if not hasattr(config, 'filter_platforms_selection') or not config.filter_platforms_selection:
|
||||
all_platform_names = sorted([p.get("platform_name", "") for p in config.platforms if p.get("platform_name")])
|
||||
config.filter_platforms_selection = [(name, name in hidden) for name in all_platform_names]
|
||||
logger.info(f"Filter platforms initialisé: {len(hidden)} plateformes cachées sur {len(all_platform_names)}")
|
||||
|
||||
# Force flush
|
||||
for handler in logging.root.handlers:
|
||||
handler.flush()
|
||||
@@ -229,14 +240,40 @@ class RGSXHandler(BaseHTTPRequestHandler):
|
||||
platforms = load_sources()
|
||||
# Ajouter le nombre de jeux depuis config.games_count
|
||||
games_count_dict = getattr(config, 'games_count', {})
|
||||
|
||||
# Filtrer les plateformes cachées selon config.filter_platforms_selection
|
||||
hidden_platforms = set()
|
||||
if hasattr(config, 'filter_platforms_selection') and config.filter_platforms_selection:
|
||||
hidden_platforms = {name for name, is_hidden in config.filter_platforms_selection if is_hidden}
|
||||
|
||||
# Ajouter aussi les plateformes sans dossier ROM (si show_unsupported_platforms = False)
|
||||
from rgsx_settings import load_rgsx_settings, get_show_unsupported_platforms
|
||||
settings = load_rgsx_settings()
|
||||
show_unsupported = get_show_unsupported_platforms(settings)
|
||||
|
||||
if not show_unsupported:
|
||||
# Masquer les plateformes dont le dossier ROM n'existe pas
|
||||
for platform in platforms:
|
||||
platform_name = platform.get('platform_name', '')
|
||||
folder = platform.get('folder', '')
|
||||
# Garder BIOS même sans dossier
|
||||
if platform_name and folder and platform_name not in ["- BIOS by TMCTV -", "- BIOS"]:
|
||||
expected_dir = os.path.join(config.ROMS_FOLDER, folder)
|
||||
if not os.path.isdir(expected_dir):
|
||||
hidden_platforms.add(platform_name)
|
||||
|
||||
filtered_platforms = []
|
||||
for platform in platforms:
|
||||
platform_name = platform.get('platform_name', '')
|
||||
platform['games_count'] = games_count_dict.get(platform_name, 0)
|
||||
# Exclure les plateformes cachées
|
||||
if platform_name not in hidden_platforms:
|
||||
platform['games_count'] = games_count_dict.get(platform_name, 0)
|
||||
filtered_platforms.append(platform)
|
||||
|
||||
self._send_json({
|
||||
'success': True,
|
||||
'count': len(platforms),
|
||||
'platforms': platforms
|
||||
'count': len(filtered_platforms),
|
||||
'platforms': filtered_platforms
|
||||
})
|
||||
|
||||
# Route: API - Recherche universelle (systèmes + jeux)
|
||||
@@ -258,12 +295,38 @@ class RGSXHandler(BaseHTTPRequestHandler):
|
||||
platforms = load_sources()
|
||||
games_count_dict = getattr(config, 'games_count', {})
|
||||
|
||||
# Filtrer les plateformes cachées selon config.filter_platforms_selection
|
||||
hidden_platforms = set()
|
||||
if hasattr(config, 'filter_platforms_selection') and config.filter_platforms_selection:
|
||||
hidden_platforms = {name for name, is_hidden in config.filter_platforms_selection if is_hidden}
|
||||
|
||||
# Ajouter aussi les plateformes sans dossier ROM (si show_unsupported_platforms = False)
|
||||
from rgsx_settings import load_rgsx_settings, get_show_unsupported_platforms
|
||||
settings = load_rgsx_settings()
|
||||
show_unsupported = get_show_unsupported_platforms(settings)
|
||||
|
||||
if not show_unsupported:
|
||||
# Masquer les plateformes dont le dossier ROM n'existe pas
|
||||
for platform in platforms:
|
||||
platform_name = platform.get('platform_name', '')
|
||||
folder = platform.get('folder', '')
|
||||
# Garder BIOS même sans dossier
|
||||
if platform_name and folder and platform_name not in ["- BIOS by TMCTV -", "- BIOS"]:
|
||||
expected_dir = os.path.join(config.ROMS_FOLDER, folder)
|
||||
if not os.path.isdir(expected_dir):
|
||||
hidden_platforms.add(platform_name)
|
||||
|
||||
matching_platforms = []
|
||||
matching_games = []
|
||||
|
||||
# Rechercher dans les plateformes et leurs jeux
|
||||
for platform in platforms:
|
||||
platform_name = platform.get('platform_name', '')
|
||||
|
||||
# Exclure les plateformes cachées
|
||||
if platform_name in hidden_platforms:
|
||||
continue
|
||||
|
||||
platform_name_lower = platform_name.lower()
|
||||
|
||||
# Vérifier si le système correspond
|
||||
@@ -347,8 +410,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 +456,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 +671,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 +842,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 +851,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 +882,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 +957,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
|
||||
@@ -2142,7 +2205,7 @@ DO NOT share this file publicly as it may contain sensitive information.
|
||||
platformsMatch.forEach(platform => {
|
||||
const imageUrl = '/api/platform-image/' + encodeURIComponent(platform.platform_name);
|
||||
html += `
|
||||
<div class="platform-card" onclick="loadGames('${platform.platform_name.replace(/'/g, "\\'")}')">
|
||||
<div class="platform-card" onclick='loadGames("${platform.platform_name.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}")'>
|
||||
<img src="${imageUrl}" alt="${platform.platform_name}" onerror="this.src='/favicon.ico'">
|
||||
<h3>${platform.platform_name}</h3>
|
||||
<p>${platform.games_count} ${t('web_games')}</p>
|
||||
@@ -2170,7 +2233,7 @@ DO NOT share this file publicly as it may contain sensitive information.
|
||||
for (const [platformName, games] of Object.entries(gamesByPlatform)) {
|
||||
html += `
|
||||
<div style="margin-bottom: 15px; background: white; padding: 15px; border-radius: 5px; border: 1px solid #ddd;">
|
||||
<h5 style="margin: 0 0 10px 0; color: #007bff; cursor: pointer;" onclick="loadGames('${platformName.replace(/'/g, "\\'")}')">
|
||||
<h5 style="margin: 0 0 10px 0; color: #007bff; cursor: pointer;" onclick='loadGames("${platformName.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}")'>
|
||||
📁 ${platformName} (${games.length})
|
||||
</h5>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
@@ -2184,8 +2247,8 @@ DO NOT share this file publicly as it may contain sensitive information.
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
${game.size ? `<span style="background: #667eea; color: white; padding: 5px 10px; border-radius: 5px; font-size: 0.9em; white-space: nowrap;">${game.size}</span>` : '<span></span>'}
|
||||
<div class="download-btn-group" style="display: flex; gap: 4px;">
|
||||
<button class="download-btn" title="${downloadTitle} (now)" onclick="downloadGame('${platformName.replace(/'/g, "\\'")}', '${game.game_name.replace(/'/g, "\\'")}', null, 'now')" style="background: transparent; color: #28a745; border: none; padding: 8px; border-radius: 5px; cursor: pointer; font-size: 1.5em; min-width: 40px;">⬇️</button>
|
||||
<button class="download-btn" title="${downloadTitle} (queue)" onclick="downloadGame('${platformName.replace(/'/g, "\\'")}', '${game.game_name.replace(/'/g, "\\'")}', null, 'queue')" style="background: transparent; color: #28a745; border: none; padding: 8px; border-radius: 5px; cursor: pointer; font-size: 1.5em; min-width: 40px;">➕</button>
|
||||
<button class="download-btn" title="${downloadTitle} (now)" onclick='downloadGame("${platformName.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", "${game.game_name.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", null, "now")' style="background: transparent; color: #28a745; border: none; padding: 8px; border-radius: 5px; cursor: pointer; font-size: 1.5em; min-width: 40px;">⬇️</button>
|
||||
<button class="download-btn" title="${downloadTitle} (queue)" onclick='downloadGame("${platformName.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", "${game.game_name.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", null, "queue")' style="background: transparent; color: #28a745; border: none; padding: 8px; border-radius: 5px; cursor: pointer; font-size: 1.5em; min-width: 40px;">➕</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2323,7 +2386,7 @@ DO NOT share this file publicly as it may contain sensitive information.
|
||||
data.platforms.forEach(p => {
|
||||
let gameCountText = t('web_game_count', '📦', p.games_count || 0);
|
||||
html += `
|
||||
<div class="platform-card" onclick="loadGames('${p.platform_name.replace(/'/g, "\\\\'")}')">
|
||||
<div class="platform-card" onclick='loadGames("${p.platform_name.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}")'>
|
||||
<img src="/api/image/${encodeURIComponent(p.platform_name)}"
|
||||
alt="${p.platform_name}"
|
||||
onerror="this.src='/api/image/default'">
|
||||
@@ -2397,8 +2460,8 @@ DO NOT share this file publicly as it may contain sensitive information.
|
||||
<span class="game-name">${g.name}</span>
|
||||
${g.size ? `<span class="game-size">${g.size}</span>` : ''}
|
||||
<div class="download-btn-group" style="display: flex; gap: 4px;">
|
||||
<button class="download-btn" title="${downloadTitle} (now)" onclick="downloadGame('${platform.replace(/'/g, "\\'")}', '${g.name.replace(/'/g, "\\'")}', ${idx}, 'now')">⬇️</button>
|
||||
<button class="download-btn" title="${downloadTitle} (queue)" onclick="downloadGame('${platform.replace(/'/g, "\\'")}', '${g.name.replace(/'/g, "\\'")}', ${idx}, 'queue')" style="background: #e0e0e0; color: #333;">➕</button>
|
||||
<button class="download-btn" title="${downloadTitle} (now)" onclick='downloadGame("${platform.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", "${g.name.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", ${idx}, "now")'>⬇️</button>
|
||||
<button class="download-btn" title="${downloadTitle} (queue)" onclick='downloadGame("${platform.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", "${g.name.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", ${idx}, "queue")' style="background: #e0e0e0; color: #333;">➕</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -2567,7 +2630,7 @@ DO NOT share this file publicly as it may contain sensitive information.
|
||||
<div class="info-item">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<strong>📥 ${fileName}${platformInfo}</strong>
|
||||
<button class="btn-action" onclick="cancelDownload('${url.replace(/'/g, "\\\\'")}', this)" title="${t('web_cancel')}">
|
||||
<button class="btn-action" onclick='cancelDownload("${url.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", this)' title="${t('web_cancel')}">
|
||||
❌
|
||||
</button>
|
||||
</div>
|
||||
@@ -2635,7 +2698,7 @@ DO NOT share this file publicly as it may contain sensitive information.
|
||||
<div class="info-item">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<strong>📥 ${fileName}${platformInfo}</strong>
|
||||
<button class="btn-action" onclick="cancelDownload('${url.replace(/'/g, "\\\\'")}', this)" title="${t('web_cancel')}">
|
||||
<button class="btn-action" onclick='cancelDownload("${url.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", this)' title="${t('web_cancel')}">
|
||||
❌
|
||||
</button>
|
||||
</div>
|
||||
@@ -2704,7 +2767,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;">
|
||||
@@ -2713,7 +2776,7 @@ DO NOT share this file publicly as it may contain sensitive information.
|
||||
Platform: ${platform} | Status: ${status}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-action" onclick="removeFromQueue('${item.task_id.replace(/'/g, "\\\\'")}', this)" title="${t('web_remove')}">
|
||||
<button class="btn-action" onclick='removeFromQueue("${item.task_id.replace(/["']/g, function(m){return m==="\\"" ? """ : "'"})}", this)' title="${t('web_remove')}">
|
||||
❌
|
||||
</button>
|
||||
</div>
|
||||
@@ -2814,8 +2877,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';
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -963,6 +1105,16 @@ def _handle_special_platforms(dest_dir, archive_path, before_dirs, iso_before=No
|
||||
if not success:
|
||||
return False, error_msg
|
||||
|
||||
# ScummVM: organisation en dossiers + fichier .scummvm
|
||||
scummvm_dir = os.path.join(config.ROMS_FOLDER, "scummvm")
|
||||
if dest_dir == scummvm_dir:
|
||||
expected_base = os.path.splitext(os.path.basename(archive_path))[0]
|
||||
# Utiliser before_items si fourni, sinon before_dirs pour rétro-compatibilité
|
||||
items_before = before_items if before_items is not None else before_dirs
|
||||
success, error_msg = handle_scummvm(dest_dir, items_before, extracted_basename=expected_base)
|
||||
if not success:
|
||||
return False, error_msg
|
||||
|
||||
return True, None
|
||||
|
||||
def extract_zip(zip_path, dest_dir, url):
|
||||
@@ -1052,7 +1204,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}")
|
||||
@@ -1547,6 +1698,88 @@ def handle_dos(dest_dir, before_items, extracted_basename=None):
|
||||
return False, error_msg
|
||||
|
||||
|
||||
def handle_scummvm(dest_dir, before_items, extracted_basename=None):
|
||||
"""Gère l'organisation spécifique des jeux ScummVM extraits.
|
||||
|
||||
- Crée un sous-dossier avec le nom du jeu (sans extension)
|
||||
- Extrait/déplace le contenu du ZIP dans ce dossier
|
||||
- Crée un fichier .scummvm vide avec le même nom
|
||||
|
||||
Exemple: Freddi_fish_1.zip -> dossier Freddi_fish_1/ + fichier Freddi_fish_1.scummvm
|
||||
|
||||
Args:
|
||||
dest_dir: Dossier de destination (scummvm)
|
||||
before_items: Set des éléments présents avant extraction
|
||||
extracted_basename: Nom de base du ZIP extrait (sans extension)
|
||||
"""
|
||||
logger.debug(f"Traitement spécifique ScummVM dans: {dest_dir}")
|
||||
time.sleep(2) # Petite latence post-extraction
|
||||
|
||||
try:
|
||||
# Déterminer les nouveaux éléments extraits
|
||||
after_items = set(os.listdir(dest_dir))
|
||||
except Exception:
|
||||
after_items = set()
|
||||
|
||||
ignore_names = {"scummvm", "images", "videos", "manuals", "media"}
|
||||
# Filtrer les nouveaux éléments (fichiers ou dossiers)
|
||||
new_items = [item for item in (after_items - before_items)
|
||||
if item not in ignore_names and not item.endswith('.scummvm')]
|
||||
|
||||
if not new_items:
|
||||
logger.warning("Aucun nouveau contenu ScummVM détecté après extraction")
|
||||
return True, None
|
||||
|
||||
if not extracted_basename:
|
||||
logger.warning("Nom de base du ZIP non fourni pour le traitement ScummVM")
|
||||
return True, None
|
||||
|
||||
# Nom du dossier et du fichier .scummvm
|
||||
game_folder_name = extracted_basename
|
||||
game_folder_path = os.path.join(dest_dir, game_folder_name)
|
||||
scummvm_file_path = os.path.join(game_folder_path, f"{game_folder_name}.scummvm")
|
||||
|
||||
try:
|
||||
# Créer le dossier du jeu s'il n'existe pas
|
||||
if os.path.exists(game_folder_path):
|
||||
logger.warning(f"Le dossier {game_folder_path} existe déjà, il sera utilisé")
|
||||
else:
|
||||
os.makedirs(game_folder_path, exist_ok=True)
|
||||
logger.debug(f"Dossier créé: {game_folder_path}")
|
||||
|
||||
# Déplacer tous les nouveaux éléments dans le dossier du jeu
|
||||
for item in new_items:
|
||||
src_path = os.path.join(dest_dir, item)
|
||||
dst_path = os.path.join(game_folder_path, item)
|
||||
|
||||
try:
|
||||
if os.path.isdir(src_path):
|
||||
shutil.move(src_path, dst_path)
|
||||
else:
|
||||
shutil.move(src_path, dst_path)
|
||||
logger.debug(f"Déplacé: {item} -> {game_folder_name}/{item}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur déplacement {item}: {e}")
|
||||
return False, f"Erreur lors du déplacement de {item}: {str(e)}"
|
||||
|
||||
# Créer le fichier .scummvm vide dans le sous-dossier
|
||||
try:
|
||||
with open(scummvm_file_path, 'w', encoding='utf-8') as f:
|
||||
pass # Fichier vide
|
||||
logger.info(f"Fichier .scummvm créé: {scummvm_file_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur création fichier .scummvm: {e}")
|
||||
return False, f"Erreur lors de la création du fichier .scummvm: {str(e)}"
|
||||
|
||||
logger.info(f"Contenu ScummVM organisé avec succès: dossier {game_folder_name}/ avec fichier {game_folder_name}.scummvm à l'intérieur")
|
||||
return True, None
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Erreur lors de l'organisation ScummVM dans {game_folder_path}: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
|
||||
def handle_xbox(dest_dir, iso_files, url=None):
|
||||
"""Gère la conversion des fichiers Xbox extraits et met à jour l'UI (Converting)."""
|
||||
logger.debug(f"Traitement spécifique Xbox dans: {dest_dir}")
|
||||
@@ -1617,7 +1850,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 +1883,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 +1934,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 +1943,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 +2142,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."""
|
||||
|
||||
3
version.json
Normal file
3
version.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": "2.3.1.4"
|
||||
}
|
||||
@@ -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
BIN
windows/python.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user