forked from Mirrors/RGSX
v1.9.5 bug 1fichier et musique repetée resolu
This commit is contained in:
569
__main__.py
569
__main__.py
@@ -1,19 +1,18 @@
|
||||
import pygame# type: ignore
|
||||
import os
|
||||
os.environ["SDL_FBDEV"] = "/dev/fb0"
|
||||
import pygame # type: ignore
|
||||
import asyncio
|
||||
import platform
|
||||
import logging
|
||||
import requests
|
||||
import config
|
||||
from config import logger
|
||||
from display import init_display, draw_loading_screen, draw_error_screen, draw_platform_grid, draw_progress_screen, draw_controls, draw_gradient, draw_virtual_keyboard, draw_popup_result_download, draw_extension_warning, draw_pause_menu, draw_controls_help, draw_game_list, draw_history_list, draw_clear_history_dialog, draw_confirm_dialog, draw_redownload_game_cache_dialog, draw_popup, THEME_COLORS, draw_music_popup
|
||||
from network import test_internet, download_rom, check_extension_before_download, extract_zip, check_for_updates
|
||||
from display import init_display, draw_loading_screen, draw_error_screen, draw_platform_grid, draw_progress_screen, draw_controls, draw_virtual_keyboard, draw_popup_result_download, draw_extension_warning, draw_pause_menu, draw_controls_help, draw_game_list, draw_history_list, draw_clear_history_dialog, draw_confirm_dialog, draw_redownload_game_cache_dialog, draw_popup, draw_gradient, THEME_COLORS
|
||||
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates
|
||||
from controls import handle_controls, validate_menu_state
|
||||
from controls_mapper import load_controls_config, map_controls, draw_controls_mapping, ACTIONS
|
||||
from utils import play_random_music, load_sources, detect_non_pc
|
||||
from utils import detect_non_pc, load_sources, check_extension_before_download, extract_zip, play_random_music
|
||||
from history import load_history
|
||||
from config import OTA_data_ZIP
|
||||
import config
|
||||
from config import OTA_VERSION_ENDPOINT, OTA_UPDATE_SCRIPT, OTA_data_ZIP
|
||||
|
||||
# Configuration du logging
|
||||
log_dir = "/userdata/roms/ports/RGSX/logs"
|
||||
@@ -34,44 +33,35 @@ except Exception as e:
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Initialisation de Pygame
|
||||
# Initialisation de Pygame et des polices
|
||||
pygame.init()
|
||||
config.init_font()
|
||||
pygame.joystick.init()
|
||||
pygame.mouse.set_visible(True)
|
||||
|
||||
# Détection du système
|
||||
# Détection du système non-PC
|
||||
config.is_non_pc = detect_non_pc()
|
||||
|
||||
# Initialisation des polices
|
||||
try:
|
||||
config.font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 36)
|
||||
config.title_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 48)
|
||||
config.search_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 48)
|
||||
config.progress_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 36)
|
||||
config.small_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 28)
|
||||
logger.debug("Police Pixel-UniCode chargée")
|
||||
except:
|
||||
config.font = pygame.font.SysFont("arial", 48)
|
||||
config.title_font = pygame.font.SysFont("arial", 60)
|
||||
config.search_font = pygame.font.SysFont("arial", 60)
|
||||
config.progress_font = pygame.font.SysFont("arial", 36)
|
||||
config.small_font = pygame.font.SysFont("arial", 28)
|
||||
logger.debug("Police Arial chargée")
|
||||
|
||||
# Initialisation de l’écran
|
||||
screen = init_display()
|
||||
pygame.display.set_caption("RGSX")
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
# Afficher un écran de chargement initial
|
||||
draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"])
|
||||
loading_text = config.font.render("Initialisation...", True, (255, 255, 255))
|
||||
text_rect = loading_text.get_rect(center=(config.screen_width // 2, config.screen_height // 2))
|
||||
screen.blit(loading_text, text_rect)
|
||||
pygame.display.flip()
|
||||
logger.debug("Écran de chargement initial affiché")
|
||||
|
||||
|
||||
# Initialisation des polices
|
||||
try:
|
||||
config.font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 36) # Police principale
|
||||
config.title_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 48) # Police pour les titres
|
||||
config.search_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 48) # Police pour la recherche
|
||||
config.progress_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 36) # Police pour l'affichage de la progression
|
||||
config.small_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 28) # Police pour les petits textes
|
||||
logger.debug("Police Pixel-UniCode chargée")
|
||||
except:
|
||||
config.font = pygame.font.SysFont("arial", 48) # Police fallback
|
||||
config.title_font = pygame.font.SysFont("arial", 60) # Police fallback pour les titres
|
||||
config.search_font = pygame.font.SysFont("arial", 60) # Police fallback pour la recherche
|
||||
config.progress_font = pygame.font.SysFont("arial", 36) # Police fallback pour l'affichage de la progression
|
||||
config.small_font = pygame.font.SysFont("arial", 28) # Police fallback pour les petits textes
|
||||
logger.debug("Police Arial chargée")
|
||||
|
||||
# Mise à jour de la résolution dans config
|
||||
config.screen_width, config.screen_height = pygame.display.get_surface().get_size()
|
||||
@@ -83,6 +73,25 @@ config.selected_platform = 0
|
||||
config.selected_key = (0, 0)
|
||||
config.transition_state = "none"
|
||||
|
||||
# Initialisation des variables de répétition
|
||||
config.repeat_action = None
|
||||
config.repeat_key = None
|
||||
config.repeat_start_time = 0
|
||||
config.repeat_last_action = 0
|
||||
|
||||
# Initialisation des variables pour la popup de musique
|
||||
current_music_name = None
|
||||
music_popup_start_time = 0
|
||||
# Dossier musique Batocera
|
||||
music_folder = "/userdata/roms/ports/RGSX/assets/music"
|
||||
music_files = [f for f in os.listdir(music_folder) if f.lower().endswith(('.ogg', '.mp3'))]
|
||||
current_music = None # Variable pour suivre la musique en cours
|
||||
if music_files:
|
||||
current_music = play_random_music(music_files, music_folder, current_music)
|
||||
else:
|
||||
logger.debug("Aucune musique trouvée dans /userdata/roms/ports/RGSX/assets/music")
|
||||
|
||||
|
||||
# Chargement de l'historique
|
||||
config.history = load_history()
|
||||
logger.debug(f"Historique chargé: {len(config.history)} entrées")
|
||||
@@ -101,14 +110,14 @@ if pygame.joystick.get_count() > 0:
|
||||
joystick.init()
|
||||
logger.debug("Gamepad initialisé")
|
||||
|
||||
# Initialisation de pygame.mixer
|
||||
# Initialisation du mixer Pygame
|
||||
pygame.mixer.pre_init(44100, -16, 2, 4096)
|
||||
pygame.mixer.init()
|
||||
|
||||
# Jouer la première musique au démarrage
|
||||
play_random_music()
|
||||
|
||||
# Boucle principale
|
||||
async def main():
|
||||
global current_music, music_files, music_folder
|
||||
logger.debug("Début main")
|
||||
running = True
|
||||
loading_step = "none"
|
||||
@@ -117,33 +126,27 @@ async def main():
|
||||
config.debounce_delay = 50
|
||||
config.update_triggered = False
|
||||
last_redraw_time = pygame.time.get_ticks()
|
||||
config.last_frame_time = pygame.time.get_ticks() # Initialisation pour éviter erreur
|
||||
|
||||
screen = init_display()
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
# Variables pour la progression simulée
|
||||
check_ota_start_time = None
|
||||
load_sources_start_time = None
|
||||
SIMULATED_CHECK_OTA_DURATION = 5.0
|
||||
SIMULATED_LOAD_SOURCES_DURATION = 3.0
|
||||
|
||||
while running:
|
||||
clock.tick(60)
|
||||
clock.tick(60) # Limite à 60 FPS
|
||||
if config.update_triggered:
|
||||
logger.debug("Mise à jour déclenchée, arrêt de la boucle principale")
|
||||
break
|
||||
|
||||
current_time = pygame.time.get_ticks()
|
||||
current_time_sec = current_time / 1000.0
|
||||
|
||||
# Forcer redraw toutes les 100 ms dans download_progress
|
||||
if config.menu_state == "download_progress" and current_time - last_redraw_time >= 100:
|
||||
config.needs_redraw = True
|
||||
last_redraw_time = current_time
|
||||
|
||||
# Gestion du popup timer
|
||||
delta_time = current_time - config.last_frame_time
|
||||
config.last_frame_time = current_time
|
||||
# Gestion de la fin du popup
|
||||
if config.menu_state == "restart_popup" and config.popup_timer > 0:
|
||||
config.popup_timer -= delta_time
|
||||
config.popup_timer -= (current_time - config.last_frame_time)
|
||||
config.needs_redraw = True
|
||||
if config.popup_timer <= 0:
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
@@ -155,15 +158,18 @@ async def main():
|
||||
# Gestion des événements
|
||||
events = pygame.event.get()
|
||||
for event in events:
|
||||
if event.type == pygame.USEREVENT + 1: # Événement de fin de musique
|
||||
logger.debug("Fin de la musique détectée, lecture d'une nouvelle musique aléatoire")
|
||||
current_music = play_random_music(music_files, music_folder, current_music)
|
||||
continue
|
||||
|
||||
if event.type == pygame.QUIT:
|
||||
config.menu_state = "confirm_exit"
|
||||
config.confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug("Événement QUIT détecté, passage à confirm_exit")
|
||||
continue
|
||||
elif event.type == pygame.USEREVENT + 1:
|
||||
logger.debug("Fin de la musique actuelle, passage à la suivante")
|
||||
play_random_music()
|
||||
|
||||
start_config = config.controls_config.get("start", {})
|
||||
if start_config and (
|
||||
(event.type == pygame.KEYDOWN and start_config.get("type") == "key" and event.key == start_config.get("value")) or
|
||||
@@ -179,9 +185,9 @@ async def main():
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Ouverture menu pause depuis {config.previous_menu_state}")
|
||||
continue
|
||||
|
||||
|
||||
if config.menu_state == "pause_menu":
|
||||
handle_controls(event, sources, joystick, screen)
|
||||
action = handle_controls(event, sources, joystick, screen)
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Événement transmis à handle_controls dans pause_menu: {event.type}")
|
||||
continue
|
||||
@@ -201,12 +207,13 @@ async def main():
|
||||
continue
|
||||
|
||||
if config.menu_state == "confirm_clear_history":
|
||||
handle_controls(event, sources, joystick, screen)
|
||||
action = handle_controls(event, sources, joystick, screen)
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Événement transmis à handle_controls dans confirm_clear_history: {event.type}")
|
||||
continue
|
||||
|
||||
if config.menu_state == "redownload_game_cache":
|
||||
handle_controls(event, sources, joystick, screen)
|
||||
action = handle_controls(event, sources, joystick, screen)
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Événement transmis à handle_controls dans redownload_game_cache: {event.type}")
|
||||
continue
|
||||
@@ -223,21 +230,57 @@ async def main():
|
||||
platform = config.platforms[config.current_platform]
|
||||
url = game[1] if isinstance(game, (list, tuple)) and len(game) > 1 else None
|
||||
if url:
|
||||
logger.debug(f"Vérification de l'extension pour {game_name}, URL: {url}")
|
||||
is_supported, message, is_zip_non_supported = check_extension_before_download(url, platform, game_name)
|
||||
if not is_supported:
|
||||
config.pending_download = (url, platform, game_name, is_zip_non_supported)
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Extension non reconnue, passage à extension_warning pour {game_name}")
|
||||
logger.debug(f"Vérification pour {game_name}, URL: {url}")
|
||||
if is_1fichier_url(url):
|
||||
if not config.API_KEY_1FICHIER:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "error"
|
||||
config.error_message = (
|
||||
"Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt"
|
||||
)
|
||||
config.needs_redraw = True
|
||||
logger.error("Clé API 1fichier absente")
|
||||
config.pending_download = None
|
||||
continue
|
||||
is_supported, message, is_zip_non_supported = check_extension_before_download(url, platform, game_name)
|
||||
if not is_supported:
|
||||
config.pending_download = (url, platform, game_name, is_zip_non_supported)
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Extension non reconnue pour lien 1fichier, passage à extension_warning pour {game_name}")
|
||||
else:
|
||||
config.previous_menu_state = config.menu_state
|
||||
logger.debug(f"Previous menu state défini: {config.previous_menu_state}")
|
||||
success, message = download_from_1fichier(url, platform, game_name, is_zip_non_supported)
|
||||
config.download_result_message = message
|
||||
config.download_result_error = not success
|
||||
config.download_result_start_time = pygame.time.get_ticks()
|
||||
config.menu_state = "download_result"
|
||||
config.download_progress.clear()
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Téléchargement 1fichier terminé pour {game_name}, succès={success}, message={message}")
|
||||
else:
|
||||
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported))
|
||||
config.download_tasks[task] = (task, url, game_name, platform)
|
||||
config.menu_state = "download_progress"
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Téléchargement démarré pour {game_name}, passage à download_progress")
|
||||
is_supported, message, is_zip_non_supported = check_extension_before_download(url, platform, game_name)
|
||||
if not is_supported:
|
||||
config.pending_download = (url, platform, game_name, is_zip_non_supported)
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Extension non reconnue, passage à extension_warning pour {game_name}")
|
||||
else:
|
||||
config.previous_menu_state = config.menu_state
|
||||
logger.debug(f"Previous menu state défini: {config.previous_menu_state}")
|
||||
success, message = download_rom(url, platform, game_name, is_zip_non_supported)
|
||||
config.download_result_message = message
|
||||
config.download_result_error = not success
|
||||
config.download_result_start_time = pygame.time.get_ticks()
|
||||
config.menu_state = "download_result"
|
||||
config.download_progress.clear()
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Téléchargement terminé pour {game_name}, succès={success}, message={message}")
|
||||
elif action == "redownload" and config.menu_state == "history" and config.history:
|
||||
entry = config.history[config.current_history_item]
|
||||
platform = entry["platform"]
|
||||
@@ -245,20 +288,57 @@ async def main():
|
||||
for game in config.games:
|
||||
if game[0] == game_name and config.platforms[config.current_platform] == platform:
|
||||
url = game[1]
|
||||
is_supported, message, is_zip_non_supported = check_extension_before_download(url, platform, game_name)
|
||||
if not is_supported:
|
||||
config.pending_download = (url, platform, game_name, is_zip_non_supported)
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Extension non reconnue pour retéléchargement, passage à extension_warning pour {game_name}")
|
||||
logger.debug(f"Vérification pour retéléchargement de {game_name}, URL: {url}")
|
||||
if is_1fichier_url(url):
|
||||
if not config.API_KEY_1FICHIER:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "error"
|
||||
config.error_message = (
|
||||
"Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt"
|
||||
)
|
||||
config.needs_redraw = True
|
||||
logger.error("Clé API 1fichier absente")
|
||||
config.pending_download = None
|
||||
continue
|
||||
is_supported, message, is_zip_non_supported = check_extension_before_download(url, platform, game_name)
|
||||
if not is_supported:
|
||||
config.pending_download = (url, platform, game_name, is_zip_non_supported)
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Extension non reconnue pour lien 1fichier, passage à extension_warning pour {game_name}")
|
||||
else:
|
||||
config.previous_menu_state = config.menu_state
|
||||
logger.debug(f"Previous menu state défini: {config.previous_menu_state}")
|
||||
success, message = download_from_1fichier(url, platform, game_name, is_zip_non_supported)
|
||||
config.download_result_message = message
|
||||
config.download_result_error = not success
|
||||
config.download_result_start_time = pygame.time.get_ticks()
|
||||
config.menu_state = "download_result"
|
||||
config.download_progress.clear()
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Retéléchargement 1fichier terminé pour {game_name}, succès={success}, message={message}")
|
||||
else:
|
||||
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported))
|
||||
config.download_tasks[task] = (task, url, game_name, platform)
|
||||
config.menu_state = "download_progress"
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Retéléchargement démarré pour {game_name}, passage à download_progress")
|
||||
is_supported, message, is_zip_non_supported = check_extension_before_download(url, platform, game_name)
|
||||
if not is_supported:
|
||||
config.pending_download = (url, platform, game_name, is_zip_non_supported)
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Extension non reconnue pour retéléchargement, passage à extension_warning pour {game_name}")
|
||||
else:
|
||||
config.previous_menu_state = config.menu_state
|
||||
logger.debug(f"Previous menu state défini: {config.previous_menu_state}")
|
||||
success, message = download_rom(url, platform, game_name, is_zip_non_supported)
|
||||
config.download_result_message = message
|
||||
config.download_result_error = not success
|
||||
config.download_result_start_time = pygame.time.get_ticks()
|
||||
config.menu_state = "download_result"
|
||||
config.download_progress.clear()
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Retéléchargement terminé pour {game_name}, succès={success}, message={message}")
|
||||
break
|
||||
|
||||
# Gestion des téléchargements
|
||||
@@ -295,197 +375,6 @@ async def main():
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Fin popup download_result, retour à {config.menu_state}")
|
||||
|
||||
# Gestion de l'état loading
|
||||
if config.menu_state == "loading":
|
||||
logger.debug(f"Étape chargement : {loading_step}")
|
||||
if loading_step == "none":
|
||||
loading_step = "init_sources"
|
||||
config.current_loading_system = "Chargement des sources..."
|
||||
config.loading_progress = 0.0
|
||||
config.needs_redraw = True
|
||||
load_sources_start_time = current_time_sec
|
||||
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
|
||||
|
||||
elif loading_step == "init_sources":
|
||||
if load_sources_start_time is None:
|
||||
load_sources_start_time = current_time_sec
|
||||
|
||||
# Simuler la progression pour init_sources
|
||||
elapsed = current_time_sec - load_sources_start_time
|
||||
progress = min(0.0 + (5.0 * elapsed / SIMULATED_LOAD_SOURCES_DURATION), 5.0)
|
||||
config.loading_progress = progress
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Progression simulée init_sources : {config.loading_progress}%")
|
||||
|
||||
# Exécuter load_sources
|
||||
sources = load_sources()
|
||||
if not sources:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Échec du chargement de sources.json"
|
||||
config.needs_redraw = True
|
||||
logger.debug("Erreur : Échec du chargement de sources.json")
|
||||
else:
|
||||
loading_step = "test_internet"
|
||||
config.current_loading_system = "Test de connexion..."
|
||||
config.loading_progress = 5.0
|
||||
load_sources_start_time = None
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
|
||||
|
||||
elif loading_step == "test_internet":
|
||||
logger.debug("Exécution de test_internet()")
|
||||
if test_internet():
|
||||
loading_step = "check_ota"
|
||||
config.current_loading_system = "Vérification des mises à jour..."
|
||||
config.loading_progress = 5.0
|
||||
check_ota_start_time = current_time_sec
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
|
||||
else:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Pas de connexion Internet. Vérifiez votre réseau."
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Erreur : {config.error_message}")
|
||||
|
||||
elif loading_step == "check_ota":
|
||||
if check_ota_start_time is None:
|
||||
check_ota_start_time = current_time_sec
|
||||
|
||||
# Simuler la progression pour check_ota
|
||||
elapsed = current_time_sec - check_ota_start_time
|
||||
progress = min(5.0 + (25.0 * elapsed / SIMULATED_CHECK_OTA_DURATION), 30.0)
|
||||
config.loading_progress = progress
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Progression simulée check_ota : {config.loading_progress}%")
|
||||
|
||||
# Exécuter check_for_updates
|
||||
success, message = await check_for_updates()
|
||||
logger.debug(f"Résultat de check_for_updates : success={success}, message={message}")
|
||||
if not success:
|
||||
config.menu_state = "error"
|
||||
config.error_message = message
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Erreur OTA : {message}")
|
||||
else:
|
||||
loading_step = "check_data"
|
||||
config.current_loading_system = "Téléchargement des jeux et images..."
|
||||
config.loading_progress = 30.0
|
||||
check_ota_start_time = None
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
|
||||
|
||||
elif loading_step == "check_data":
|
||||
games_data_dir = "/userdata/roms/ports/RGSX/games"
|
||||
is_data_empty = not os.path.exists(games_data_dir) or not any(os.scandir(games_data_dir))
|
||||
|
||||
if is_data_empty:
|
||||
config.current_loading_system = "Téléchargement du Dossier Data initial..."
|
||||
config.loading_progress = 30.0
|
||||
config.needs_redraw = True
|
||||
logger.debug("Dossier Data vide, début du téléchargement du ZIP")
|
||||
|
||||
try:
|
||||
zip_path = "/userdata/roms/ports/RGSX.zip"
|
||||
headers = {'User-Agent': 'Mozilla/5.0'}
|
||||
with requests.get(OTA_data_ZIP, stream=True, headers=headers, timeout=30) as response:
|
||||
response.raise_for_status()
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
logger.debug(f"Taille totale du ZIP : {total_size} octets")
|
||||
downloaded = 0
|
||||
os.makedirs(os.path.dirname(zip_path), exist_ok=True)
|
||||
with open(zip_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
config.download_progress[OTA_data_ZIP] = {
|
||||
"downloaded_size": downloaded,
|
||||
"total_size": total_size,
|
||||
"status": "Téléchargement",
|
||||
"progress_percent": (downloaded / total_size * 100) if total_size > 0 else 0
|
||||
}
|
||||
config.loading_progress = 30.0 + (40.0 * downloaded / total_size) if total_size > 0 else 30.0
|
||||
config.needs_redraw = True
|
||||
await asyncio.sleep(0)
|
||||
logger.debug(f"ZIP téléchargé : {zip_path}")
|
||||
|
||||
config.current_loading_system = "Extraction du Dossier Data initial..."
|
||||
config.loading_progress = 70.0
|
||||
config.needs_redraw = True
|
||||
dest_dir = "/userdata/roms/ports/RGSX"
|
||||
success, message = extract_zip(zip_path, dest_dir, OTA_data_ZIP)
|
||||
if success:
|
||||
logger.debug(f"Extraction réussie : {message}")
|
||||
config.loading_progress = 70.0
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
raise Exception(f"Échec de l'extraction : {message}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du téléchargement/extraction du Dossier Data : {str(e)}")
|
||||
config.menu_state = "error"
|
||||
config.error_message = f"Échec du téléchargement/extraction du Dossier Data : {str(e)}"
|
||||
config.needs_redraw = True
|
||||
loading_step = "load_sources"
|
||||
if os.path.exists(zip_path):
|
||||
os.remove(zip_path)
|
||||
continue
|
||||
|
||||
if os.path.exists(zip_path):
|
||||
os.remove(zip_path)
|
||||
logger.debug(f"Fichier ZIP {zip_path} supprimé")
|
||||
|
||||
loading_step = "load_sources"
|
||||
config.current_loading_system = "Chargement des systèmes..."
|
||||
config.loading_progress = 70.0
|
||||
load_sources_start_time = current_time_sec
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
|
||||
|
||||
else:
|
||||
loading_step = "load_sources"
|
||||
config.current_loading_system = "Chargement des systèmes..."
|
||||
config.loading_progress = 70.0
|
||||
load_sources_start_time = current_time_sec
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Dossier Data non vide, passage à {loading_step}")
|
||||
|
||||
elif loading_step == "load_sources":
|
||||
if load_sources_start_time is None:
|
||||
load_sources_start_time = current_time_sec
|
||||
|
||||
# Simuler la progression pour load_sources
|
||||
elapsed = current_time_sec - load_sources_start_time
|
||||
progress = min(70.0 + (30.0 * elapsed / SIMULATED_LOAD_SOURCES_DURATION), 100.0)
|
||||
config.loading_progress = progress
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Progression simulée load_sources : {config.loading_progress}%")
|
||||
|
||||
# Exécuter load_sources
|
||||
sources = load_sources()
|
||||
if not sources:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Échec du chargement de sources.json"
|
||||
config.needs_redraw = True
|
||||
logger.debug("Erreur : Échec du chargement de sources.json")
|
||||
else:
|
||||
config.menu_state = "platform"
|
||||
config.loading_progress = 0.0
|
||||
config.current_loading_system = ""
|
||||
load_sources_start_time = None
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Fin chargement, passage à platform, progress={config.loading_progress}")
|
||||
|
||||
# Gestion de l'état de transition
|
||||
if config.transition_state == "to_game":
|
||||
config.transition_progress += 1
|
||||
if config.transition_progress >= config.transition_duration:
|
||||
config.menu_state = "game"
|
||||
config.transition_state = "idle"
|
||||
config.transition_progress = 0.0
|
||||
config.needs_redraw = True
|
||||
logger.debug("Transition terminée, passage à game")
|
||||
|
||||
# Affichage
|
||||
if config.needs_redraw:
|
||||
draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"])
|
||||
@@ -513,6 +402,7 @@ async def main():
|
||||
draw_extension_warning(screen)
|
||||
elif config.menu_state == "pause_menu":
|
||||
draw_pause_menu(screen, config.selected_option)
|
||||
logger.debug("Rendu de draw_pause_menu")
|
||||
elif config.menu_state == "controls_help":
|
||||
draw_controls_help(screen, config.previous_menu_state)
|
||||
elif config.menu_state == "history":
|
||||
@@ -529,12 +419,12 @@ async def main():
|
||||
config.needs_redraw = True
|
||||
logger.error(f"État de menu non valide détecté: {config.menu_state}, retour à platform")
|
||||
draw_controls(screen, config.menu_state)
|
||||
draw_music_popup(screen)
|
||||
pygame.display.flip()
|
||||
config.needs_redraw = False
|
||||
|
||||
# Gestion de l'état controls_mapping
|
||||
if config.menu_state == "controls_mapping":
|
||||
logger.debug("Avant appel de map_controls")
|
||||
try:
|
||||
success = map_controls(screen)
|
||||
logger.debug(f"map_controls terminé, succès={success}")
|
||||
@@ -542,6 +432,7 @@ async def main():
|
||||
config.controls_config = load_controls_config()
|
||||
config.menu_state = "loading"
|
||||
config.needs_redraw = True
|
||||
logger.debug("Passage à l'état loading après mappage")
|
||||
else:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Échec du mappage des contrôles"
|
||||
@@ -553,6 +444,134 @@ async def main():
|
||||
config.error_message = f"Erreur dans map_controls: {str(e)}"
|
||||
config.needs_redraw = True
|
||||
|
||||
# Gestion de l'état loading
|
||||
elif config.menu_state == "loading":
|
||||
if loading_step == "none":
|
||||
loading_step = "test_internet"
|
||||
config.current_loading_system = "Test de connexion..."
|
||||
config.loading_progress = 0.0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
|
||||
elif loading_step == "test_internet":
|
||||
logger.debug("Exécution de test_internet()")
|
||||
if test_internet():
|
||||
loading_step = "check_ota"
|
||||
config.current_loading_system = "Mise à jour en cours... Patientez si l'ecran reste figé.. Puis relancer l'application une fois qu'elle est terminée."
|
||||
config.loading_progress = 20.0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
|
||||
else:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Pas de connexion Internet. Vérifiez votre réseau."
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Erreur : {config.error_message}")
|
||||
elif loading_step == "check_ota":
|
||||
logger.debug("Exécution de check_for_updates()")
|
||||
success, message = await check_for_updates()
|
||||
logger.debug(f"Résultat de check_for_updates : success={success}, message={message}")
|
||||
if not success:
|
||||
config.menu_state = "error"
|
||||
config.error_message = message
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Erreur OTA : {message}")
|
||||
else:
|
||||
loading_step = "check_data"
|
||||
config.current_loading_system = "Téléchargement des jeux et images ..."
|
||||
config.loading_progress = 50.0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
|
||||
elif loading_step == "check_data":
|
||||
games_data_dir = "/userdata/roms/ports/RGSX/games"
|
||||
is_data_empty = not os.path.exists(games_data_dir) or not any(os.scandir(games_data_dir))
|
||||
if is_data_empty:
|
||||
config.current_loading_system = "Téléchargement du Dossier Data initial..."
|
||||
config.loading_progress = 30.0
|
||||
config.needs_redraw = True
|
||||
logger.debug("Dossier Data vide, début du téléchargement du ZIP")
|
||||
try:
|
||||
zip_path = "/userdata/roms/ports/RGSX.zip"
|
||||
headers = {'User-Agent': 'Mozilla/5.0'}
|
||||
with requests.get(OTA_data_ZIP, stream=True, headers=headers, timeout=30) as response:
|
||||
response.raise_for_status()
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
logger.debug(f"Taille totale du ZIP : {total_size} octets")
|
||||
downloaded = 0
|
||||
os.makedirs(os.path.dirname(zip_path), exist_ok=True)
|
||||
with open(zip_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
config.download_progress[OTA_data_ZIP] = {
|
||||
"downloaded_size": downloaded,
|
||||
"total_size": total_size,
|
||||
"status": "Téléchargement",
|
||||
"progress_percent": (downloaded / total_size * 100) if total_size > 0 else 0
|
||||
}
|
||||
config.loading_progress = 15.0 + (35.0 * downloaded / total_size) if total_size > 0 else 15.0
|
||||
config.needs_redraw = True
|
||||
await asyncio.sleep(0)
|
||||
logger.debug(f"ZIP téléchargé : {zip_path}")
|
||||
|
||||
config.current_loading_system = "Extraction du Dossier Data initial..."
|
||||
config.loading_progress = 60.0
|
||||
config.needs_redraw = True
|
||||
dest_dir = "/userdata/roms/ports/RGSX"
|
||||
success, message = extract_zip(zip_path, dest_dir, OTA_data_ZIP)
|
||||
if success:
|
||||
logger.debug(f"Extraction réussie : {message}")
|
||||
config.loading_progress = 70.0
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
raise Exception(f"Échec de l'extraction : {message}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du téléchargement/extraction du Dossier Data : {str(e)}")
|
||||
config.menu_state = "error"
|
||||
config.error_message = f"Échec du téléchargement/extraction du Dossier Data : {str(e)}"
|
||||
config.needs_redraw = True
|
||||
loading_step = "load_sources"
|
||||
if os.path.exists(zip_path):
|
||||
os.remove(zip_path)
|
||||
continue
|
||||
if os.path.exists(zip_path):
|
||||
os.remove(zip_path)
|
||||
logger.debug(f"Fichier ZIP {zip_path} supprimé")
|
||||
loading_step = "load_sources"
|
||||
config.current_loading_system = "Chargement des systèmes..."
|
||||
config.loading_progress = 80.0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
|
||||
else:
|
||||
loading_step = "load_sources"
|
||||
config.current_loading_system = "Chargement des systèmes..."
|
||||
config.loading_progress = 80.0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Dossier Data non vide, passage à {loading_step}")
|
||||
elif loading_step == "load_sources":
|
||||
sources = load_sources()
|
||||
if not sources:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Échec du chargement de sources.json"
|
||||
config.needs_redraw = True
|
||||
logger.debug("Erreur : Échec du chargement de sources.json")
|
||||
else:
|
||||
config.menu_state = "platform"
|
||||
config.loading_progress = 100.0
|
||||
config.current_loading_system = ""
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Fin chargement, passage à platform, progress={config.loading_progress}")
|
||||
|
||||
# Gestion de l'état de transition
|
||||
if config.transition_state == "to_game":
|
||||
config.transition_progress += 1
|
||||
if config.transition_progress >= config.transition_duration:
|
||||
config.menu_state = "game"
|
||||
config.transition_state = "idle"
|
||||
config.transition_progress = 0.0
|
||||
config.needs_redraw = True
|
||||
logger.debug("Transition terminée, passage à game")
|
||||
|
||||
config.last_frame_time = current_time
|
||||
clock.tick(60)
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
|
||||
27
config.py
27
config.py
@@ -5,7 +5,8 @@ import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "1.9.4"
|
||||
app_version = "1.9.5"
|
||||
|
||||
|
||||
# URL du serveur OTA
|
||||
OTA_SERVER_URL = "https://retrogamesets.fr/softs"
|
||||
@@ -13,17 +14,22 @@ OTA_VERSION_ENDPOINT = f"{OTA_SERVER_URL}/version.json"
|
||||
OTA_UPDATE_SCRIPT = f"{OTA_SERVER_URL}/rgsx-update.sh"
|
||||
OTA_data_ZIP = f"{OTA_SERVER_URL}/rgsx-data.zip"
|
||||
|
||||
# Constantes pour la répétition automatique dans pause_menu
|
||||
REPEAT_DELAY = 300 # Délai initial avant répétition (ms)
|
||||
REPEAT_INTERVAL = 150 # Intervalle entre répétitions (ms), augmenté pour réduire la fréquence
|
||||
REPEAT_ACTION_DEBOUNCE = 100 # Délai anti-rebond pour répétitions (ms), augmenté pour éviter les répétitions excessives
|
||||
|
||||
|
||||
# Variables d'état
|
||||
platforms = [] # Liste des plateformes chargées depuis sources.json
|
||||
current_platform = 0 # Index de la plateforme actuellement sélectionnée
|
||||
platforms = []
|
||||
current_platform = 0
|
||||
platform_names = {} # {platform_id: platform_name}
|
||||
games = [] # Liste des jeux chargés pour la plateforme actuelle
|
||||
current_game = 0 # Index du jeu actuellement sélectionné
|
||||
menu_state = "" # État actuel du menu (par exemple, "main_menu", "game_list", "settings", etc.)
|
||||
games = []
|
||||
current_game = 0
|
||||
menu_state = "popup"
|
||||
confirm_choice = False
|
||||
scroll_offset = 0
|
||||
visible_games = 15
|
||||
visible_games = 15
|
||||
popup_start_time = 0
|
||||
last_progress_update = 0
|
||||
needs_redraw = True
|
||||
@@ -39,6 +45,10 @@ download_result_start_time = 0
|
||||
loading_progress = 0.0
|
||||
current_loading_system = ""
|
||||
error_message = ""
|
||||
repeat_action = None
|
||||
repeat_start_time = 0
|
||||
repeat_last_action = 0
|
||||
repeat_key = None
|
||||
filtered_games = []
|
||||
search_mode = False
|
||||
search_query = ""
|
||||
@@ -90,6 +100,9 @@ CONTROLS_CONFIG_PATH = "/userdata/saves/ports/rgsx/controls.json"
|
||||
"""Chemin du fichier de configuration des contrôles."""
|
||||
HISTORY_PATH = "/userdata/saves/ports/rgsx/history.json"
|
||||
"""Chemin du fichier de l'historique des téléchargements."""
|
||||
JSON_EXTENSIONS = "/userdata/roms/ports/RGSX/rom_extensions.json"
|
||||
"""Chemin du fichier JSON des extensions de ROMs."""
|
||||
|
||||
|
||||
def init_font():
|
||||
"""Initialise les polices après pygame.init()."""
|
||||
|
||||
233
controls.py
233
controls.py
@@ -6,19 +6,19 @@ import asyncio
|
||||
import json
|
||||
import os
|
||||
from display import draw_validation_transition
|
||||
from network import download_rom, check_extension_before_download, download_from_1fichier, is_1fichier_url, is_extension_supported,load_extensions_json,sanitize_filename
|
||||
from utils import load_games
|
||||
from network import download_rom, download_from_1fichier, is_1fichier_url
|
||||
from utils import load_games, check_extension_before_download, is_extension_supported, load_extensions_json, sanitize_filename
|
||||
from history import load_history, clear_history
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Constantes pour la répétition automatique
|
||||
REPEAT_DELAY = 300 # Délai initial avant répétition (ms)
|
||||
REPEAT_DELAY = 100 # Délai initial avant répétition (ms)
|
||||
REPEAT_INTERVAL = 100 # Intervalle entre répétitions (ms)
|
||||
JOYHAT_DEBOUNCE = 200 # Délai anti-rebond pour JOYHATMOTION (ms)
|
||||
JOYHAT_DEBOUNCE = 0 # Délai anti-rebond pour JOYHATMOTION (ms)
|
||||
JOYAXIS_DEBOUNCE = 50 # Délai anti-rebond pour JOYAXISMOTION (ms)
|
||||
REPEAT_ACTION_DEBOUNCE = 50 # Délai anti-rebond pour répétitions up/down/left/right (ms)
|
||||
REPEAT_ACTION_DEBOUNCE = 0 # Délai anti-rebond pour répétitions up/down/left/right (ms)
|
||||
|
||||
# Liste des états valides
|
||||
VALID_STATES = [
|
||||
@@ -177,6 +177,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.repeat_last_action = current_time
|
||||
config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
|
||||
elif is_input_matched(event, "up"):
|
||||
if current_grid_index - GRID_COLS >= 0:
|
||||
config.selected_platform -= GRID_COLS
|
||||
@@ -185,6 +186,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.repeat_last_action = current_time
|
||||
config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
|
||||
elif is_input_matched(event, "left"):
|
||||
if col > 0:
|
||||
config.selected_platform -= 1
|
||||
@@ -193,6 +195,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.repeat_last_action = current_time
|
||||
config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
|
||||
elif config.current_page > 0:
|
||||
config.current_page -= 1
|
||||
config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS + (GRID_COLS - 1)
|
||||
@@ -203,6 +206,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.repeat_last_action = current_time
|
||||
config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
|
||||
elif is_input_matched(event, "right"):
|
||||
if col < GRID_COLS - 1 and current_grid_index < max_index:
|
||||
config.selected_platform += 1
|
||||
@@ -211,6 +215,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.repeat_last_action = current_time
|
||||
config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
|
||||
elif (config.current_page + 1) * systems_per_page < len(config.platforms):
|
||||
config.current_page += 1
|
||||
config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS
|
||||
@@ -221,6 +226,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.repeat_last_action = current_time
|
||||
config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
|
||||
elif is_input_matched(event, "page_down"):
|
||||
if (config.current_page + 1) * systems_per_page < len(config.platforms):
|
||||
config.current_page += 1
|
||||
@@ -232,6 +238,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.repeat_start_time = 0
|
||||
config.repeat_last_action = current_time
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
|
||||
#logger.debug("Page suivante, répétition réinitialisée")
|
||||
elif is_input_matched(event, "page_up"):
|
||||
if config.current_page > 0:
|
||||
@@ -244,6 +251,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.repeat_start_time = 0
|
||||
config.repeat_last_action = current_time
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
|
||||
#logger.debug("Page précédente, répétition réinitialisée")
|
||||
elif is_input_matched(event, "page_up"):
|
||||
if config.current_page > 0:
|
||||
@@ -256,6 +264,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.repeat_start_time = 0
|
||||
config.repeat_last_action = current_time
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
|
||||
#logger.debug("Page précédente, répétition réinitialisée")
|
||||
elif is_input_matched(event, "progress"):
|
||||
if config.download_tasks:
|
||||
@@ -454,55 +463,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.menu_state = "history"
|
||||
config.needs_redraw = True
|
||||
logger.debug("Ouverture history depuis game")
|
||||
elif is_input_matched(event, "confirm"):
|
||||
if games:
|
||||
config.pending_download = check_extension_before_download(
|
||||
games[config.current_game][0],
|
||||
config.platforms[config.current_platform],
|
||||
games[config.current_game][1]
|
||||
)
|
||||
if config.pending_download:
|
||||
url, platform, game_name, is_zip_non_supported = config.pending_download
|
||||
is_supported = is_extension_supported(
|
||||
sanitize_filename(game_name),
|
||||
platform,
|
||||
load_extensions_json()
|
||||
)
|
||||
if not is_supported:
|
||||
config.previous_menu_state = config.menu_state # Ajouter cette ligne
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Extension non supportée, passage à extension_warning pour {game_name}")
|
||||
else:
|
||||
if is_1fichier_url(url):
|
||||
if not config.API_KEY_1FICHIER:
|
||||
config.previous_menu_state = config.menu_state # Ajouter cette ligne
|
||||
config.menu_state = "error"
|
||||
config.error_message = (
|
||||
"Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt à ouvrir dans un editeur de texte et coller la clé API"
|
||||
)
|
||||
config.needs_redraw = True
|
||||
logger.error("Clé API 1fichier absente, téléchargement impossible.")
|
||||
config.pending_download = None
|
||||
return action
|
||||
loop = asyncio.get_running_loop()
|
||||
task = loop.run_in_executor(None, download_from_1fichier, url, platform, game_name, is_zip_non_supported)
|
||||
else:
|
||||
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported))
|
||||
config.download_tasks[task] = (task, url, game_name, platform)
|
||||
config.previous_menu_state = config.menu_state # Ajouter cette ligne
|
||||
config.menu_state = "download_progress"
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Début du téléchargement: {game_name} pour {platform} depuis {url}")
|
||||
config.pending_download = None
|
||||
action = "download"
|
||||
else:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Extension non supportée ou erreur de téléchargement"
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.error(f"config.pending_download est None pour {games[config.current_game][0]}")
|
||||
|
||||
elif is_input_matched(event, "cancel"):
|
||||
config.menu_state = "platform"
|
||||
config.current_game = 0
|
||||
@@ -515,6 +476,135 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
logger.debug("Passage à redownload_game_cache depuis game")
|
||||
|
||||
# Sélectionner un jeu , evenent confirm
|
||||
elif is_input_matched(event, "confirm"):
|
||||
if games:
|
||||
url = games[config.current_game][1]
|
||||
game_name = games[config.current_game][0]
|
||||
platform = config.platforms[config.current_platform]
|
||||
logger.debug(f"Vérification pour {game_name}, URL: {url}")
|
||||
# Vérifier d'abord si c'est un lien 1fichier
|
||||
if is_1fichier_url(url):
|
||||
if not config.API_KEY_1FICHIER:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "error"
|
||||
config.error_message = (
|
||||
"Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt à ouvrir dans un editeur de texte et coller la clé API"
|
||||
)
|
||||
config.needs_redraw = True
|
||||
logger.error("Clé API 1fichier absente, téléchargement impossible.")
|
||||
config.pending_download = None
|
||||
return action
|
||||
# Vérifier l'extension pour les liens 1fichier
|
||||
config.pending_download = check_extension_before_download(url, platform, game_name)
|
||||
if config.pending_download:
|
||||
is_supported = is_extension_supported(
|
||||
sanitize_filename(game_name),
|
||||
platform,
|
||||
load_extensions_json()
|
||||
)
|
||||
if not is_supported:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Extension non supportée, passage à extension_warning pour {game_name}")
|
||||
else:
|
||||
loop = asyncio.get_running_loop()
|
||||
task = loop.run_in_executor(None, download_from_1fichier, url, platform, game_name, config.pending_download[3])
|
||||
config.download_tasks[task] = (task, url, game_name, platform)
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "download_progress"
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Début du téléchargement 1fichier: {game_name} pour {platform} depuis {url}")
|
||||
config.pending_download = None
|
||||
action = "download"
|
||||
else:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Extension non supportée ou erreur de téléchargement"
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.error(f"config.pending_download est None pour {game_name}")
|
||||
else:
|
||||
# Vérifier l'extension pour les liens non-1fichier
|
||||
config.pending_download = check_extension_before_download(url, platform, game_name)
|
||||
if config.pending_download:
|
||||
is_supported = is_extension_supported(
|
||||
sanitize_filename(game_name),
|
||||
platform,
|
||||
load_extensions_json()
|
||||
)
|
||||
if not is_supported:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Extension non supportée, passage à extension_warning pour {game_name}")
|
||||
else:
|
||||
task = asyncio.create_task(download_rom(url, platform, game_name, config.pending_download[3]))
|
||||
config.download_tasks[task] = (task, url, game_name, platform)
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "download_progress"
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Début du téléchargement: {game_name} pour {platform} depuis {url}")
|
||||
config.pending_download = None
|
||||
action = "download"
|
||||
else:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Extension non supportée ou erreur de téléchargement"
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.error(f"config.pending_download est None pour {game_name}")
|
||||
|
||||
# Avertissement extension
|
||||
elif config.menu_state == "extension_warning":
|
||||
if is_input_matched(event, "confirm"):
|
||||
if config.extension_confirm_selection == 1:
|
||||
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):
|
||||
if not config.API_KEY_1FICHIER:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "error"
|
||||
config.error_message = (
|
||||
"Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt"
|
||||
)
|
||||
config.needs_redraw = True
|
||||
logger.error("Clé API 1fichier absente, téléchargement impossible.")
|
||||
config.pending_download = None
|
||||
return action
|
||||
loop = asyncio.get_running_loop()
|
||||
task = loop.run_in_executor(None, download_from_1fichier, url, platform, game_name, is_zip_non_supported)
|
||||
else:
|
||||
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported))
|
||||
config.download_tasks[task] = (task, url, game_name, platform)
|
||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.menu_state = "download_progress"
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Téléchargement confirmé après avertissement: {game_name} pour {platform} depuis {url}")
|
||||
config.pending_download = None
|
||||
action = "download"
|
||||
else:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Données de téléchargement invalides"
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.error("config.pending_download invalide")
|
||||
else:
|
||||
config.pending_download = None
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Retour à {config.menu_state} depuis extension_warning")
|
||||
elif is_input_matched(event, "left") or is_input_matched(event, "right"):
|
||||
config.extension_confirm_selection = 1 - config.extension_confirm_selection
|
||||
config.needs_redraw = True
|
||||
#logger.debug(f"Changement sélection extension_warning: {config.extension_confirm_selection}")
|
||||
elif is_input_matched(event, "cancel"):
|
||||
config.pending_download = None
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Retour à {config.menu_state} depuis extension_warning")
|
||||
|
||||
#Historique
|
||||
elif config.menu_state == "history":
|
||||
history = config.history
|
||||
@@ -667,41 +757,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
#logger.debug(f"Changement sélection confirm_exit: {config.confirm_selection}")
|
||||
|
||||
# Avertissement extension
|
||||
elif config.menu_state == "extension_warning":
|
||||
if is_input_matched(event, "confirm"):
|
||||
if config.extension_confirm_selection == 1:
|
||||
if config.pending_download and len(config.pending_download) == 4:
|
||||
url, platform, game_name, is_zip_non_supported = config.pending_download
|
||||
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported))
|
||||
config.download_tasks[task] = (task, url, game_name, platform)
|
||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.menu_state = "download_progress"
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Téléchargement confirmé après avertissement: {game_name} pour {platform} depuis {url}")
|
||||
config.pending_download = None
|
||||
action = "download"
|
||||
else:
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Données de téléchargement invalides"
|
||||
config.pending_download = None
|
||||
config.needs_redraw = True
|
||||
logger.error("config.pending_download invalide")
|
||||
else:
|
||||
config.pending_download = None
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Retour à {config.menu_state} depuis extension_warning")
|
||||
elif is_input_matched(event, "left") or is_input_matched(event, "right"):
|
||||
config.extension_confirm_selection = 1 - config.extension_confirm_selection
|
||||
config.needs_redraw = True
|
||||
#logger.debug(f"Changement sélection extension_warning: {config.extension_confirm_selection}")
|
||||
elif is_input_matched(event, "cancel"):
|
||||
config.pending_download = None
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Retour à {config.menu_state} depuis extension_warning")
|
||||
|
||||
# Menu pause
|
||||
elif config.menu_state == "pause_menu":
|
||||
logger.debug(f"État pause_menu, selected_option={config.selected_option}, événement={event.type}, valeur={getattr(event, 'value', None)}")
|
||||
|
||||
70
display.py
70
display.py
@@ -1,6 +1,5 @@
|
||||
import pygame # type: ignore
|
||||
import config
|
||||
import os
|
||||
from utils import truncate_text_middle, wrap_text, load_system_image
|
||||
import logging
|
||||
import math
|
||||
@@ -152,7 +151,9 @@ def draw_loading_screen(screen):
|
||||
"Bienvenue dans RGSX",
|
||||
"It's dangerous to go alone, take all you need!",
|
||||
"Mais ne téléchargez que des jeux",
|
||||
"dont vous possédez les originaux !"
|
||||
"dont vous possédez les originaux !",
|
||||
"RGSX n'est pas responsable des contenus téléchargés,",
|
||||
"et n'heberge pas de ROMs.",
|
||||
]
|
||||
|
||||
margin_horizontal = int(config.screen_width * 0.025)
|
||||
@@ -699,7 +700,7 @@ def draw_popup_result_download(screen, message, is_error):
|
||||
"""Affiche une popup avec un message de résultat."""
|
||||
screen.blit(OVERLAY, (0, 0))
|
||||
if message is None:
|
||||
message = "Téléchargement annulé"
|
||||
message = "Téléchargement annulé par l'utilisateur."
|
||||
logger.debug(f"Message popup : {message}, is_error={is_error}")
|
||||
# Réduire la largeur maximale pour le wrapping
|
||||
wrapped_message = wrap_text(message, config.small_font, config.screen_width - 160)
|
||||
@@ -849,18 +850,18 @@ def draw_pause_menu(screen, selected_option):
|
||||
def draw_controls_help(screen, previous_state):
|
||||
"""Affiche la liste des contrôles avec un style moderne."""
|
||||
common_controls = {
|
||||
"confirm": lambda action: f"{get_control_display('confirm', 'Entrée/A/Croix')} : {action}",
|
||||
"cancel": lambda action: f"{get_control_display('cancel', 'Échap/B/Rond')} : {action}",
|
||||
"start": lambda: f"{get_control_display('start', 'Start/')} : Menu",
|
||||
"progress": lambda action: f"{get_control_display('progress', 'X/Carré')} : {action}",
|
||||
"confirm": lambda action: f"{get_control_display('confirm', 'Entrée/A')} : {action}",
|
||||
"cancel": lambda action: f"{get_control_display('cancel', 'Échap/B')} : {action}",
|
||||
"start": lambda: f"{get_control_display('start', 'Start')} : Menu",
|
||||
"progress": lambda action: f"{get_control_display('progress', 'X')} : {action}",
|
||||
"up": lambda action: f"{get_control_display('up', 'Flèche Haut')} : {action}",
|
||||
"down": lambda action: f"{get_control_display('down', 'Flèche Bas')} : {action}",
|
||||
"page_up": lambda action: f"{get_control_display('page_up', 'Q/LB/L1')} : {action}",
|
||||
"page_down": lambda action: f"{get_control_display('page_down', 'E/RB/R1')} : {action}",
|
||||
"page_up": lambda action: f"{get_control_display('page_up', 'Q/LB')} : {action}",
|
||||
"page_down": lambda action: f"{get_control_display('page_down', 'E/RB')} : {action}",
|
||||
"filter": lambda action: f"{get_control_display('filter', 'Select')} : {action}",
|
||||
"history": lambda action: f"{get_control_display('history', 'H/Y/Triangle')} : {action}",
|
||||
"delete": lambda: f"{get_control_display('delete', 'Backspace/LT/L2')} : Supprimer",
|
||||
"space": lambda: f"{get_control_display('space', 'Espace/RT/R2')} : Espace"
|
||||
"history": lambda action: f"{get_control_display('history', 'H')} : {action}",
|
||||
"delete": lambda: f"{get_control_display('delete', 'Retour Arrière')} : Supprimer",
|
||||
"space": lambda: f"{get_control_display('space', 'Espace')} : Espace"
|
||||
}
|
||||
|
||||
state_controls = {
|
||||
@@ -1041,47 +1042,4 @@ def draw_popup(screen):
|
||||
countdown_text = f"Ce message se fermera dans {remaining_time} seconde{'s' if remaining_time != 1 else ''}"
|
||||
countdown_surface = config.small_font.render(countdown_text, True, THEME_COLORS["text"])
|
||||
countdown_rect = countdown_surface.get_rect(center=(config.screen_width // 2, popup_y + margin_top_bottom + len(text_lines) * line_height + line_height // 2))
|
||||
screen.blit(countdown_surface, countdown_rect)
|
||||
|
||||
|
||||
# Variables globales pour la popup de musique
|
||||
current_music_name = None
|
||||
music_popup_start_time = None
|
||||
MUSIC_POPUP_DURATION = 5 # Durée d'affichage en secondes
|
||||
|
||||
def draw_music_popup(screen):
|
||||
"""Affiche une popup discrète en bas à droite avec le nom de la musique en cours."""
|
||||
global current_music_name, music_popup_start_time
|
||||
|
||||
if current_music_name is None or music_popup_start_time is None:
|
||||
return
|
||||
|
||||
# Vérifier si la popup doit encore être affichée
|
||||
current_time = pygame.time.get_ticks() / 1000 # Temps en secondes
|
||||
if current_time - music_popup_start_time > MUSIC_POPUP_DURATION:
|
||||
current_music_name = None
|
||||
music_popup_start_time = None
|
||||
return
|
||||
|
||||
# Paramètres de la popup
|
||||
font = config.small_font
|
||||
text = font.render(current_music_name, True, THEME_COLORS["text"])
|
||||
text_width, text_height = font.size(current_music_name)
|
||||
padding = 10
|
||||
rect_width = text_width + 2 * padding
|
||||
rect_height = text_height + 2 * padding
|
||||
rect_x = config.screen_width - rect_width - 22 # 20 pixels de marge à droite
|
||||
rect_y = config.screen_height - rect_height - 8 # 20 pixels de marge en bas
|
||||
|
||||
# Créer une surface semi-transparente
|
||||
popup_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA)
|
||||
pygame.draw.rect(popup_surface, THEME_COLORS["fond_image"] + (180,), (0, 0, rect_width, rect_height), border_radius=8)
|
||||
pygame.draw.rect(popup_surface, THEME_COLORS["border"] + (200,), (0, 0, rect_width, rect_height), 1, border_radius=8)
|
||||
|
||||
# Ajouter le texte
|
||||
text_rect = text.get_rect(center=(rect_width // 2, rect_height // 2))
|
||||
popup_surface.blit(text, text_rect)
|
||||
|
||||
# Afficher la popup
|
||||
screen.blit(popup_surface, (rect_x, rect_y))
|
||||
|
||||
screen.blit(countdown_surface, countdown_rect)
|
||||
428
network.py
428
network.py
@@ -1,348 +1,123 @@
|
||||
import requests
|
||||
import subprocess
|
||||
import re
|
||||
import os
|
||||
import threading
|
||||
import pygame # type: ignore
|
||||
import zipfile
|
||||
import json
|
||||
import sys
|
||||
import asyncio
|
||||
import config
|
||||
import sys
|
||||
from config import OTA_VERSION_ENDPOINT, OTA_UPDATE_SCRIPT
|
||||
from utils import sanitize_filename
|
||||
from utils import sanitize_filename, extract_zip, extract_rar
|
||||
from history import add_to_history, load_history
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
JSON_EXTENSIONS = "/userdata/roms/ports/RGSX/rom_extensions.json"
|
||||
|
||||
cache = {}
|
||||
CACHE_TTL = 3600 # 1 heure
|
||||
|
||||
def test_internet():
|
||||
"""Teste la connexion Internet dans un thread séparé."""
|
||||
logger.debug("Test de connexion Internet")
|
||||
result = [False]
|
||||
|
||||
def ping_thread():
|
||||
try:
|
||||
proc = subprocess.run(['ping', '-c', '4', '8.8.8.8'], capture_output=True, text=True, timeout=5)
|
||||
result[0] = proc.returncode == 0
|
||||
logger.debug("Connexion Internet OK" if result[0] else "Échec ping 8.8.8.8")
|
||||
except Exception as e:
|
||||
logger.debug(f"Erreur test Internet: {str(e)}")
|
||||
result[0] = False
|
||||
|
||||
thread = threading.Thread(target=ping_thread)
|
||||
thread.start()
|
||||
thread.join()
|
||||
return result[0]
|
||||
try:
|
||||
result = subprocess.run(['ping', '-c', '4', '8.8.8.8'], capture_output=True, text=True, timeout=5)
|
||||
if result.returncode == 0:
|
||||
logger.debug("Connexion Internet OK")
|
||||
return True
|
||||
else:
|
||||
logger.debug("Échec ping 8.8.8.8")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.debug(f"Erreur test Internet: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
# Fonction pour vérifier et appliquer les mises à jour OTA
|
||||
async def check_for_updates():
|
||||
"""Vérifie et applique les mises à jour OTA dans un thread séparé."""
|
||||
result = [None, None]
|
||||
|
||||
def update_thread():
|
||||
try:
|
||||
logger.debug("Vérification de la version disponible sur le serveur")
|
||||
config.current_loading_system = "Mise à jour en cours... Patientez l'écran reste figé..Puis relancer l'application"
|
||||
config.loading_progress = 5.0
|
||||
try:
|
||||
logger.debug("Vérification de la version disponible sur le serveur")
|
||||
config.current_loading_system = "Mise à jour en cours... Patientez l'ecran reste figé..Puis relancer l'application"
|
||||
config.loading_progress = 5.0
|
||||
config.needs_redraw = True
|
||||
response = requests.get(OTA_VERSION_ENDPOINT, timeout=5)
|
||||
response.raise_for_status()
|
||||
if response.headers.get("content-type") != "application/json":
|
||||
raise ValueError(f"Le fichier version.json n'est pas un JSON valide (type de contenu : {response.headers.get('content-type')})")
|
||||
version_data = response.json()
|
||||
latest_version = version_data.get("version")
|
||||
logger.debug(f"Version distante : {latest_version}, version locale : {config.app_version}")
|
||||
|
||||
if latest_version != config.app_version:
|
||||
config.current_loading_system = f"Mise à jour disponible : {latest_version}"
|
||||
config.loading_progress = 10.0
|
||||
config.needs_redraw = True
|
||||
response = requests.get(OTA_VERSION_ENDPOINT, timeout=5)
|
||||
response.raise_for_status()
|
||||
if response.headers.get("content-type") != "application/json":
|
||||
raise ValueError(f"Le fichier version.json n'est pas un JSON valide (type de contenu : {response.headers.get('content-type')})")
|
||||
version_data = response.json()
|
||||
latest_version = version_data.get("version")
|
||||
logger.debug(f"Version distante : {latest_version}, version locale : {config.app_version}")
|
||||
logger.debug(f"Téléchargement du script de mise à jour : {OTA_UPDATE_SCRIPT}")
|
||||
|
||||
if latest_version != config.app_version:
|
||||
config.current_loading_system = f"Mise à jour disponible : {latest_version}"
|
||||
config.loading_progress = 10.0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Téléchargement du script de mise à jour : {OTA_UPDATE_SCRIPT}")
|
||||
update_script_path = "/userdata/roms/ports/rgsx-update.sh"
|
||||
logger.debug(f"Téléchargement de {OTA_UPDATE_SCRIPT} vers {update_script_path}")
|
||||
with requests.get(OTA_UPDATE_SCRIPT, stream=True, timeout=10) as r:
|
||||
r.raise_for_status()
|
||||
with open(update_script_path, "wb") as f:
|
||||
for chunk in r.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
config.loading_progress = min(50.0, config.loading_progress + 5.0)
|
||||
config.needs_redraw = True
|
||||
await asyncio.sleep(0)
|
||||
|
||||
update_script_path = "/userdata/roms/ports/rgsx-update.sh"
|
||||
logger.debug(f"Téléchargement de {OTA_UPDATE_SCRIPT} vers {update_script_path}")
|
||||
with requests.get(OTA_UPDATE_SCRIPT, stream=True, timeout=10) as r:
|
||||
r.raise_for_status()
|
||||
with open(update_script_path, "wb") as f:
|
||||
for chunk in r.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
config.loading_progress = min(50.0, config.loading_progress + 5.0)
|
||||
config.needs_redraw = True
|
||||
# Pas de sleep ici, car on est dans un thread
|
||||
|
||||
config.current_loading_system = "Préparation de la mise à jour..."
|
||||
config.loading_progress = 60.0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Rendre {update_script_path} exécutable")
|
||||
subprocess.run(["chmod", "+x", update_script_path], check=True)
|
||||
logger.debug(f"Script {update_script_path} rendu exécutable")
|
||||
|
||||
logger.debug(f"Vérification de l'existence et des permissions de {update_script_path}")
|
||||
if not os.path.isfile(update_script_path):
|
||||
logger.error(f"Le script {update_script_path} n'existe pas")
|
||||
result[0], result[1] = False, f"Erreur : le script {update_script_path} n'existe pas"
|
||||
return
|
||||
if not os.access(update_script_path, os.X_OK):
|
||||
logger.error(f"Le script {update_script_path} n'est pas exécutable")
|
||||
result[0], result[1] = False, f"Erreur : le script {update_script_path} n'est pas exécutable"
|
||||
return
|
||||
|
||||
wrapper_script_path = "/userdata/roms/ports/RGSX/update/run.update"
|
||||
logger.debug(f"Vérification de l'existence et des permissions de {wrapper_script_path}")
|
||||
if not os.path.isfile(wrapper_script_path):
|
||||
logger.error(f"Le script wrapper {wrapper_script_path} n'existe pas")
|
||||
result[0], result[1] = False, f"Erreur : le script wrapper {wrapper_script_path} n'existe pas"
|
||||
return
|
||||
if not os.access(wrapper_script_path, os.X_OK):
|
||||
logger.error(f"Le script wrapper {wrapper_script_path} n'est pas exécutable")
|
||||
subprocess.run(["chmod", "+x", wrapper_script_path], check=True)
|
||||
logger.debug(f"Script wrapper {wrapper_script_path} rendu exécutable")
|
||||
|
||||
logger.debug("Désactivation des événements Pygame QUIT")
|
||||
pygame.event.set_blocked(pygame.QUIT)
|
||||
|
||||
config.current_loading_system = "Application de la mise à jour..."
|
||||
config.loading_progress = 80.0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Exécution du script wrapper : {wrapper_script_path}")
|
||||
os_result = os.system(f"{wrapper_script_path} &")
|
||||
logger.debug(f"Résultat de os.system : {os_result}")
|
||||
if os_result != 0:
|
||||
logger.error(f"Échec du lancement du script wrapper : code de retour {os_result}")
|
||||
result[0], result[1] = False, f"Échec du lancement du script wrapper : code de retour {os_result}"
|
||||
return
|
||||
|
||||
config.current_loading_system = "Mise à jour déclenchée, redémarrage..."
|
||||
config.loading_progress = 100.0
|
||||
config.needs_redraw = True
|
||||
logger.debug("Mise à jour déclenchée, arrêt de l'application")
|
||||
config.update_triggered = True
|
||||
pygame.quit()
|
||||
sys.exit(0)
|
||||
else:
|
||||
logger.debug("Aucune mise à jour logicielle disponible")
|
||||
result[0], result[1] = True, "Aucune mise à jour disponible"
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur OTA : {str(e)}")
|
||||
result[0], result[1] = False, f"Erreur lors de la vérification des mises à jour : {str(e)}"
|
||||
|
||||
thread = threading.Thread(target=update_thread)
|
||||
thread.start()
|
||||
while thread.is_alive():
|
||||
pygame.event.pump()
|
||||
await asyncio.sleep(0.1)
|
||||
thread.join()
|
||||
return result[0], result[1]
|
||||
|
||||
def load_extensions_json():
|
||||
"""Charge le fichier JSON contenant les extensions supportées."""
|
||||
try:
|
||||
with open(JSON_EXTENSIONS, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la lecture de {JSON_EXTENSIONS}: {e}")
|
||||
return []
|
||||
|
||||
def is_extension_supported(filename, platform, extensions_data):
|
||||
"""Vérifie si l'extension du fichier est supportée pour la plateforme donnée."""
|
||||
extension = os.path.splitext(filename)[1].lower()
|
||||
dest_dir = None
|
||||
for platform_dict in config.platform_dicts:
|
||||
if platform_dict["platform"] == platform:
|
||||
dest_dir = platform_dict.get("folder")
|
||||
break
|
||||
if not dest_dir:
|
||||
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
|
||||
dest_dir = os.path.join("/userdata/roms", platform)
|
||||
for system in extensions_data:
|
||||
if system["folder"] == dest_dir:
|
||||
return extension in system["extensions"]
|
||||
logger.warning(f"Aucun système trouvé pour le dossier {dest_dir}")
|
||||
return False
|
||||
|
||||
def extract_zip(zip_path, dest_dir, url):
|
||||
"""Extrait le contenu du fichier ZIP dans le dossier cible avec un suivi progressif de la progression."""
|
||||
try:
|
||||
lock = threading.Lock()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
total_size = sum(info.file_size for info in zip_ref.infolist() if not info.is_dir())
|
||||
logger.info(f"Taille totale à extraire: {total_size} octets")
|
||||
if total_size == 0:
|
||||
logger.warning("ZIP vide ou ne contenant que des dossiers")
|
||||
return True, "ZIP vide extrait avec succès"
|
||||
|
||||
extracted_size = 0
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
chunk_size = 8192
|
||||
for info in zip_ref.infolist():
|
||||
if info.is_dir():
|
||||
continue
|
||||
file_path = os.path.join(dest_dir, info.filename)
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with zip_ref.open(info) as source, open(file_path, 'wb') as dest:
|
||||
file_size = info.file_size
|
||||
file_extracted = 0
|
||||
while True:
|
||||
chunk = source.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
dest.write(chunk)
|
||||
file_extracted += len(chunk)
|
||||
extracted_size += len(chunk)
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = extracted_size
|
||||
config.download_progress[url]["total_size"] = total_size
|
||||
config.download_progress[url]["status"] = "Extracting"
|
||||
config.download_progress[url]["progress_percent"] = (extracted_size / total_size * 100) if total_size > 0 else 0
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
logger.debug(f"Extraction {info.filename}, chunk: {len(chunk)}, file_extracted: {file_extracted}/{file_size}, total_extracted: {extracted_size}/{total_size}, progression: {(extracted_size/total_size*100):.1f}%")
|
||||
os.chmod(file_path, 0o644)
|
||||
|
||||
for root, dirs, _ in os.walk(dest_dir):
|
||||
for dir_name in dirs:
|
||||
os.chmod(os.path.join(root, dir_name), 0o755)
|
||||
|
||||
os.remove(zip_path)
|
||||
logger.info(f"Fichier ZIP {zip_path} extrait dans {dest_dir} et supprimé")
|
||||
return True, "ZIP extrait avec succès"
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'extraction de {zip_path}: {e}")
|
||||
return False, str(e)
|
||||
|
||||
def extract_rar(rar_path, dest_dir, url):
|
||||
"""Extrait le contenu du fichier RAR dans le dossier cible, préservant la structure des dossiers."""
|
||||
try:
|
||||
lock = threading.Lock()
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
|
||||
result = subprocess.run(['unrar'], capture_output=True, text=True)
|
||||
if result.returncode not in [0, 1]:
|
||||
logger.error("Commande unrar non disponible")
|
||||
return False, "Commande unrar non disponible"
|
||||
|
||||
result = subprocess.run(['unrar', 'l', '-v', rar_path], capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr.strip()
|
||||
logger.error(f"Erreur lors de la liste des fichiers RAR: {error_msg}")
|
||||
return False, f"Échec de la liste des fichiers RAR: {error_msg}"
|
||||
|
||||
logger.debug(f"Sortie brute de 'unrar l -v {rar_path}':\n{result.stdout}")
|
||||
|
||||
total_size = 0
|
||||
files_to_extract = []
|
||||
root_dirs = set()
|
||||
lines = result.stdout.splitlines()
|
||||
in_file_list = False
|
||||
for line in lines:
|
||||
if line.startswith("----"):
|
||||
in_file_list = not in_file_list
|
||||
continue
|
||||
if in_file_list:
|
||||
match = re.match(r'^\s*(\S+)\s+(\d+)\s+\d*\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})\s+(.+)$', line)
|
||||
if match:
|
||||
attrs = match.group(1)
|
||||
file_size = int(match.group(2))
|
||||
file_date = match.group(3)
|
||||
file_name = match.group(4).strip()
|
||||
if 'D' not in attrs:
|
||||
files_to_extract.append((file_name, file_size))
|
||||
total_size += file_size
|
||||
root_dir = file_name.split('/')[0] if '/' in file_name else ''
|
||||
if root_dir:
|
||||
root_dirs.add(root_dir)
|
||||
logger.debug(f"Ligne parsée: {file_name}, taille: {file_size}, date: {file_date}")
|
||||
else:
|
||||
logger.debug(f"Dossier ignoré: {file_name}")
|
||||
else:
|
||||
logger.debug(f"Ligne ignorée (format inattendu): {line}")
|
||||
|
||||
logger.info(f"Taille totale à extraire (RAR): {total_size} octets")
|
||||
logger.debug(f"Fichiers à extraire: {files_to_extract}")
|
||||
logger.debug(f"Dossiers racines détectés: {root_dirs}")
|
||||
if total_size == 0:
|
||||
logger.warning("RAR vide, ne contenant que des dossiers, ou erreur de parsing")
|
||||
return False, "RAR vide ou erreur lors de la liste des fichiers"
|
||||
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = 0
|
||||
config.download_progress[url]["total_size"] = total_size
|
||||
config.download_progress[url]["status"] = "Extracting"
|
||||
config.download_progress[url]["progress_percent"] = 0
|
||||
config.current_loading_system = "Préparation de la mise à jour..."
|
||||
config.loading_progress = 60.0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Rendre {update_script_path} exécutable")
|
||||
subprocess.run(["chmod", "+x", update_script_path], check=True)
|
||||
logger.debug(f"Script {update_script_path} rendu exécutable")
|
||||
|
||||
escaped_rar_path = rar_path.replace(" ", "\\ ")
|
||||
escaped_dest_dir = dest_dir.replace(" ", "\\ ")
|
||||
process = subprocess.Popen(['unrar', 'x', '-y', escaped_rar_path, escaped_dest_dir],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
stdout, stderr = process.communicate()
|
||||
logger.debug(f"Vérification de l'existence et des permissions de {update_script_path}")
|
||||
if not os.path.isfile(update_script_path):
|
||||
logger.error(f"Le script {update_script_path} n'existe pas")
|
||||
return False, f"Erreur : le script {update_script_path} n'existe pas"
|
||||
if not os.access(update_script_path, os.X_OK):
|
||||
logger.error(f"Le script {update_script_path} n'est pas exécutable")
|
||||
return False, f"Erreur : le script {update_script_path} n'est pas exécutable"
|
||||
|
||||
if process.returncode != 0:
|
||||
logger.error(f"Erreur lors de l'extraction de {rar_path}: {stderr}")
|
||||
return False, f"Erreur lors de l'extraction: {stderr}"
|
||||
wrapper_script_path = "/userdata/roms/ports/RGSX/update/run.update"
|
||||
logger.debug(f"Vérification de l'existence et des permissions de {wrapper_script_path}")
|
||||
if not os.path.isfile(wrapper_script_path):
|
||||
logger.error(f"Le script wrapper {wrapper_script_path} n'existe pas")
|
||||
return False, f"Erreur : le script wrapper {wrapper_script_path} n'existe pas"
|
||||
if not os.access(wrapper_script_path, os.X_OK):
|
||||
logger.error(f"Le script wrapper {wrapper_script_path} n'est pas exécutable")
|
||||
subprocess.run(["chmod", "+x", wrapper_script_path], check=True)
|
||||
logger.debug(f"Script wrapper {wrapper_script_path} rendu exécutable")
|
||||
|
||||
extracted_size = 0
|
||||
extracted_files = []
|
||||
total_files = len(files_to_extract)
|
||||
for i, (expected_file, file_size) in enumerate(files_to_extract):
|
||||
file_path = os.path.join(dest_dir, expected_file)
|
||||
if os.path.exists(file_path):
|
||||
extracted_size += file_size
|
||||
extracted_files.append(expected_file)
|
||||
os.chmod(file_path, 0o644)
|
||||
logger.debug(f"Fichier extrait: {expected_file}, taille: {file_size}, chemin: {file_path}")
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = extracted_size
|
||||
config.download_progress[url]["status"] = "Extracting"
|
||||
config.download_progress[url]["progress_percent"] = ((i + 1) / total_files * 100) if total_files > 0 else 0
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
logger.warning(f"Fichier non trouvé après extraction: {expected_file}")
|
||||
logger.debug("Désactivation des événements Pygame QUIT")
|
||||
pygame.event.set_blocked(pygame.QUIT)
|
||||
|
||||
missing_files = [f for f, _ in files_to_extract if f not in extracted_files]
|
||||
if missing_files:
|
||||
logger.warning(f"Fichiers non extraits: {', '.join(missing_files)}")
|
||||
return False, f"Fichiers non extraits: {', '.join(missing_files)}"
|
||||
config.current_loading_system = "Application de la mise à jour..."
|
||||
config.loading_progress = 80.0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Exécution du script wrapper : {wrapper_script_path}")
|
||||
result = os.system(f"{wrapper_script_path} &")
|
||||
logger.debug(f"Résultat de os.system : {result}")
|
||||
if result != 0:
|
||||
logger.error(f"Échec du lancement du script wrapper : code de retour {result}")
|
||||
return False, f"Échec du lancement du script wrapper : code de retour {result}"
|
||||
|
||||
if dest_dir == "/userdata/roms/ps3" and len(root_dirs) == 1:
|
||||
root_dir = root_dirs.pop()
|
||||
old_path = os.path.join(dest_dir, root_dir)
|
||||
new_path = os.path.join(dest_dir, f"{root_dir}.ps3")
|
||||
if os.path.isdir(old_path):
|
||||
try:
|
||||
os.rename(old_path, new_path)
|
||||
logger.info(f"Dossier renommé: {old_path} -> {new_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du renommage de {old_path} en {new_path}: {str(e)}")
|
||||
return False, f"Erreur lors du renommage du dossier: {str(e)}"
|
||||
else:
|
||||
logger.warning(f"Dossier racine {old_path} non trouvé après extraction")
|
||||
elif dest_dir == "/userdata/roms/ps3" and len(root_dirs) > 1:
|
||||
logger.warning(f"Plusieurs dossiers racines détectés dans l'archive: {root_dirs}. Aucun renommage effectué.")
|
||||
config.current_loading_system = "Mise à jour déclenchée, redémarrage..."
|
||||
config.loading_progress = 100.0
|
||||
config.needs_redraw = True
|
||||
logger.debug("Mise à jour déclenchée, arrêt de l'application")
|
||||
config.update_triggered = True
|
||||
pygame.quit()
|
||||
sys.exit(0)
|
||||
else:
|
||||
logger.debug("Aucune mise à jour logicielle disponible")
|
||||
return True, "Aucune mise à jour disponible"
|
||||
|
||||
for root, dirs, _ in os.walk(dest_dir):
|
||||
for dir_name in dirs:
|
||||
os.chmod(os.path.join(root, dir_name), 0o755)
|
||||
|
||||
os.remove(rar_path)
|
||||
logger.info(f"Fichier RAR {rar_path} extrait dans {dest_dir} et supprimé")
|
||||
return True, "RAR extrait avec succès"
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'extraction de {rar_path}: {str(e)}")
|
||||
return False, str(e)
|
||||
finally:
|
||||
if os.path.exists(rar_path):
|
||||
try:
|
||||
os.remove(rar_path)
|
||||
logger.info(f"Fichier RAR {rar_path} supprimé après échec de l'extraction")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la suppression de {rar_path}: {str(e)}")
|
||||
logger.error(f"Erreur OTA : {str(e)}")
|
||||
return False, f"Erreur lors de la vérification des mises à jour : {str(e)}"
|
||||
|
||||
|
||||
|
||||
|
||||
async def download_rom(url, platform, game_name, is_zip_non_supported=False):
|
||||
logger.debug(f"Début téléchargement: {game_name} depuis {url}, is_zip_non_supported={is_zip_non_supported}")
|
||||
@@ -463,31 +238,6 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False):
|
||||
|
||||
return result[0], result[1]
|
||||
|
||||
def check_extension_before_download(game_name, platform, url):
|
||||
"""Vérifie l'extension avant de lancer le téléchargement et retourne un tuple de 4 éléments."""
|
||||
try:
|
||||
sanitized_name = sanitize_filename(game_name)
|
||||
extensions_data = load_extensions_json()
|
||||
if not extensions_data:
|
||||
logger.error(f"Fichier {JSON_EXTENSIONS} vide ou introuvable")
|
||||
return None
|
||||
|
||||
is_supported = is_extension_supported(sanitized_name, platform, extensions_data)
|
||||
extension = os.path.splitext(sanitized_name)[1].lower()
|
||||
is_archive = extension in (".zip", ".rar")
|
||||
|
||||
if is_supported:
|
||||
logger.debug(f"L'extension de {sanitized_name} est supportée pour {platform}")
|
||||
return (url, platform, game_name, False)
|
||||
elif is_archive:
|
||||
logger.debug(f"Archive {extension.upper()} détectée pour {sanitized_name}, extraction automatique prévue")
|
||||
return (url, platform, game_name, True)
|
||||
else:
|
||||
logger.debug(f"Extension non supportée ({extension}) pour {sanitized_name}, avertissement affiché")
|
||||
return (url, platform, game_name, False)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur vérification extension {url}: {str(e)}")
|
||||
return None
|
||||
|
||||
def is_1fichier_url(url):
|
||||
"""Détecte si l'URL est un lien 1fichier."""
|
||||
|
||||
455
utils.py
455
utils.py
@@ -3,124 +3,26 @@ import re
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import threading
|
||||
import requests
|
||||
import config
|
||||
import random
|
||||
import platform
|
||||
import subprocess
|
||||
import config
|
||||
import threading
|
||||
import zipfile
|
||||
import random
|
||||
from config import JSON_EXTENSIONS
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# Désactiver les logs DEBUG de urllib3 e requests pour supprimer les messages de connexion HTTP
|
||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||
logging.getLogger("requests").setLevel(logging.WARNING)
|
||||
|
||||
# Liste globale pour stocker les systèmes avec une erreur 404
|
||||
unavailable_systems = []
|
||||
|
||||
unavailable_systems = [] # Liste globale pour stocker les systèmes avec une erreur 404
|
||||
|
||||
def check_url(url, platform_id, unavailable_systems_lock=None, unavailable_systems=None):
|
||||
"""Vérifie si une URL est accessible via une requête HEAD."""
|
||||
try:
|
||||
response = requests.head(url, timeout=5, allow_redirects=True)
|
||||
if response.status_code == 404:
|
||||
logger.error(f"URL non accessible pour {platform_id}: {url} (code 404)")
|
||||
if unavailable_systems_lock and unavailable_systems is not None:
|
||||
with unavailable_systems_lock:
|
||||
unavailable_systems.append(platform_id)
|
||||
elif unavailable_systems is not None:
|
||||
unavailable_systems.append(platform_id)
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Erreur lors du test de l'URL pour {platform_id}: {url} ({str(e)})")
|
||||
if unavailable_systems_lock and unavailable_systems is not None:
|
||||
with unavailable_systems_lock:
|
||||
unavailable_systems.append(platform_id)
|
||||
elif unavailable_systems is not None:
|
||||
unavailable_systems.append(platform_id)
|
||||
|
||||
def load_games(platform_id, unavailable_systems_lock=None, unavailable_systems=None):
|
||||
"""Charge les jeux pour une plateforme donnée en utilisant platform_id et teste la première URL."""
|
||||
games_path = f"/userdata/roms/ports/RGSX/games/{platform_id}.json"
|
||||
try:
|
||||
with open(games_path, 'r', encoding='utf-8') as f:
|
||||
games = json.load(f)
|
||||
|
||||
# Tester la première URL si la liste n'est pas vide
|
||||
if games and len(games) > 0 and len(games[0]) > 1:
|
||||
first_url = games[0][1]
|
||||
check_url(first_url, platform_id, unavailable_systems_lock, unavailable_systems)
|
||||
else:
|
||||
logger.debug(f"Aucune URL à tester pour {platform_id} (liste vide ou mal formée)")
|
||||
|
||||
logger.debug(f"Jeux chargés pour {platform_id}: {len(games)} jeux")
|
||||
return games
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du chargement des jeux pour {platform_id}: {str(e)}")
|
||||
return []
|
||||
|
||||
def write_unavailable_systems():
|
||||
"""Écrit la liste des systèmes avec une erreur 404 dans un fichier texte."""
|
||||
if not unavailable_systems:
|
||||
logger.debug("Aucun système avec une erreur 404, aucun fichier écrit")
|
||||
return
|
||||
|
||||
from datetime import datetime
|
||||
current_time = datetime.now()
|
||||
timestamp = current_time.strftime("%d-%m-%Y-%H-%M")
|
||||
log_dir = "/userdata/roms/ports/logs/RGSX"
|
||||
log_file = f"{log_dir}/systemes_unavailable_{timestamp}.txt"
|
||||
|
||||
try:
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
with open(log_file, 'w', encoding='utf-8') as f:
|
||||
f.write("Systèmes avec une erreur 404 :\n")
|
||||
for system in unavailable_systems:
|
||||
f.write(f"{system}\n")
|
||||
logger.debug(f"Fichier écrit : {log_file} avec {len(unavailable_systems)} systèmes")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'écriture du fichier {log_file}: {str(e)}")
|
||||
|
||||
def load_sources():
|
||||
"""Charge sources.json et les jeux pour toutes les plateformes en parallèle."""
|
||||
sources_path = "/userdata/roms/ports/RGSX/sources.json"
|
||||
logger.debug(f"Chargement de {sources_path}")
|
||||
try:
|
||||
with open(sources_path, 'r', encoding='utf-8') as f:
|
||||
sources = json.load(f)
|
||||
sources = sorted(sources, key=lambda x: x.get("nom", x.get("platform", "")).lower())
|
||||
config.platforms = [source["platform"] for source in sources]
|
||||
config.platform_dicts = sources
|
||||
config.platform_names = {source["platform"]: source["nom"] for source in sources}
|
||||
config.games_count = {platform: 0 for platform in config.platforms}
|
||||
|
||||
# Créer un verrou pour unavailable_systems
|
||||
unavailable_systems_lock = threading.Lock()
|
||||
global unavailable_systems
|
||||
unavailable_systems = []
|
||||
|
||||
# Lancer les chargements des jeux en parallèle avec threading
|
||||
threads = []
|
||||
results = [None] * len(config.platforms)
|
||||
for i, platform in enumerate(config.platforms):
|
||||
thread = threading.Thread(target=lambda idx=i, plat=platform: results.__setitem__(idx, load_games(plat, unavailable_systems_lock, unavailable_systems)))
|
||||
threads.append(thread)
|
||||
thread.start()
|
||||
|
||||
# Attendre que tous les threads se terminent
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
# Mettre à jour games_count avec les résultats
|
||||
for platform, games in zip(config.platforms, results):
|
||||
if games:
|
||||
config.games_count[platform] = len(games)
|
||||
logger.debug(f"Jeux chargés pour {platform}: {len(games)} jeux")
|
||||
else:
|
||||
config.games_count[platform] = 0
|
||||
logger.error(f"Échec du chargement des jeux pour {platform}")
|
||||
|
||||
write_unavailable_systems()
|
||||
return sources
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du chargement de sources.json: {str(e)}")
|
||||
return []
|
||||
|
||||
# Détection système non-PC
|
||||
def detect_non_pc():
|
||||
@@ -137,6 +39,144 @@ def detect_non_pc():
|
||||
logger.debug(f"Système détecté: {platform.system()}, architecture: {arch}, is_non_pc={is_non_pc}")
|
||||
return is_non_pc
|
||||
|
||||
|
||||
# Fonction pour charger le fichier JSON des extensions supportées
|
||||
def load_extensions_json():
|
||||
"""Charge le fichier JSON contenant les extensions supportées."""
|
||||
try:
|
||||
with open(JSON_EXTENSIONS, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la lecture de {JSON_EXTENSIONS}: {e}")
|
||||
return []
|
||||
|
||||
def check_extension_before_download(url, platform, game_name):
|
||||
"""Vérifie l'extension avant de lancer le téléchargement et retourne un tuple de 4 éléments."""
|
||||
try:
|
||||
sanitized_name = sanitize_filename(game_name)
|
||||
extensions_data = load_extensions_json()
|
||||
if not extensions_data:
|
||||
logger.error(f"Fichier {JSON_EXTENSIONS} vide ou introuvable")
|
||||
return None
|
||||
|
||||
is_supported = is_extension_supported(sanitized_name, platform, extensions_data)
|
||||
extension = os.path.splitext(sanitized_name)[1].lower()
|
||||
is_archive = extension in (".zip", ".rar")
|
||||
|
||||
if is_supported:
|
||||
logger.debug(f"L'extension de {sanitized_name} est supportée pour {platform}")
|
||||
return (url, platform, game_name, False)
|
||||
elif is_archive:
|
||||
logger.debug(f"Archive {extension.upper()} détectée pour {sanitized_name}, extraction automatique prévue")
|
||||
return (url, platform, game_name, True)
|
||||
else:
|
||||
logger.debug(f"Extension non supportée ({extension}) pour {sanitized_name}, avertissement affiché")
|
||||
return (url, platform, game_name, False)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur vérification extension {url}: {str(e)}")
|
||||
return None
|
||||
|
||||
# Fonction pour vérifier si l'extension est supportée pour une plateforme donnée
|
||||
def is_extension_supported(filename, platform, extensions_data):
|
||||
"""Vérifie si l'extension du fichier est supportée pour la plateforme donnée."""
|
||||
extension = os.path.splitext(filename)[1].lower()
|
||||
dest_dir = None
|
||||
for platform_dict in config.platform_dicts:
|
||||
if platform_dict["platform"] == platform:
|
||||
dest_dir = platform_dict.get("folder")
|
||||
break
|
||||
if not dest_dir:
|
||||
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
|
||||
dest_dir = os.path.join("/userdata/roms", platform)
|
||||
for system in extensions_data:
|
||||
if system["folder"] == dest_dir:
|
||||
return extension in system["extensions"]
|
||||
logger.warning(f"Aucun système trouvé pour le dossier {dest_dir}")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
# Fonction pour charger sources.json
|
||||
def load_sources():
|
||||
"""Charge les sources depuis sources.json et initialise les plateformes."""
|
||||
sources_path = "/userdata/roms/ports/RGSX/sources.json"
|
||||
logger.debug(f"Chargement de {sources_path}")
|
||||
try:
|
||||
with open(sources_path, 'r', encoding='utf-8') as f:
|
||||
sources = json.load(f)
|
||||
sources = sorted(sources, key=lambda x: x.get("nom", x.get("platform", "")).lower())
|
||||
config.platforms = [source["platform"] for source in sources]
|
||||
config.platform_dicts = sources
|
||||
config.platform_names = {source["platform"]: source["nom"] for source in sources}
|
||||
config.games_count = {platform: 0 for platform in config.platforms} # Initialiser à 0
|
||||
# Charger les jeux pour chaque plateforme
|
||||
loaded_platforms = set() # Pour suivre les plateformes déjà loguées
|
||||
for platform in config.platforms:
|
||||
games = load_games(platform)
|
||||
config.games_count[platform] = len(games)
|
||||
if platform not in loaded_platforms:
|
||||
loaded_platforms.add(platform)
|
||||
# Appeler write_unavailable_systems une seule fois après la boucle
|
||||
write_unavailable_systems() # Assurez-vous que cette fonction est définie
|
||||
return sources
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du chargement de sources.json : {str(e)}")
|
||||
return []
|
||||
|
||||
def load_games(platform_id):
|
||||
"""Charge les jeux pour une plateforme donnée en utilisant platform_id et teste la première URL."""
|
||||
games_path = f"/userdata/roms/ports/RGSX/games/{platform_id}.json"
|
||||
try:
|
||||
with open(games_path, 'r', encoding='utf-8') as f:
|
||||
games = json.load(f)
|
||||
|
||||
# Tester la première URL si la liste n'est pas vide
|
||||
if games and len(games) > 0 and len(games[0]) > 1:
|
||||
first_url = games[0][1]
|
||||
try:
|
||||
response = requests.head(first_url, timeout=5, allow_redirects=True)
|
||||
if response.status_code not in (200, 303): # Ne logger que les codes autres que 200 et 303
|
||||
logger.debug(f"https://{first_url} \"HEAD {first_url} HTTP/1.1\" {response.status_code} 0")
|
||||
if response.status_code == 404:
|
||||
logger.error(f"URL non accessible pour {platform_id} : {first_url} (code 404)")
|
||||
unavailable_systems.append(platform_id) # Assurez-vous que unavailable_systems est défini
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Erreur lors du test de l'URL pour {platform_id} : {first_url} ({str(e)})")
|
||||
else:
|
||||
logger.debug(f"Aucune URL à tester pour {platform_id} (liste vide ou mal formée)")
|
||||
|
||||
logger.debug(f"Jeux chargés pour {platform_id}: {len(games)} jeux")
|
||||
return games
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du chargement des jeux pour {platform_id} : {str(e)}")
|
||||
return []
|
||||
|
||||
def write_unavailable_systems():
|
||||
"""Écrit la liste des systèmes avec une erreur 404 dans un fichier texte."""
|
||||
if not unavailable_systems:
|
||||
logger.debug("Aucun système avec des liens HS, rien à écrire dans le fichier.")
|
||||
return
|
||||
|
||||
# Formater la date et l'heure pour le nom du fichier
|
||||
current_time = datetime.now()
|
||||
timestamp = current_time.strftime("%d-%m-%Y-%H-%M")
|
||||
log_dir = "/userdata/roms/ports/logs/RGSX"
|
||||
log_file = f"{log_dir}/systemes_unavailable_{timestamp}.txt"
|
||||
|
||||
try:
|
||||
# Créer le répertoire s'il n'existe pas
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
# Écrire les systèmes dans le fichier
|
||||
with open(log_file, 'w', encoding='utf-8') as f:
|
||||
f.write("Systèmes avec une erreur 404 :\n")
|
||||
for system in unavailable_systems:
|
||||
f.write(f"{system}\n")
|
||||
logger.debug(f"Fichier écrit : {log_file} avec {len(unavailable_systems)} systèmes")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'écriture du fichier {log_file} : {str(e)}")
|
||||
|
||||
|
||||
def truncate_text_middle(text, font, max_width):
|
||||
"""Tronque le texte en insérant '...' au milieu, en préservant le début et la fin, sans extension de fichier."""
|
||||
@@ -264,15 +304,190 @@ def load_system_image(platform_dict):
|
||||
logger.error(f"Erreur lors du chargement de l'image pour {platform_name} : {str(e)}")
|
||||
return None
|
||||
|
||||
# Dossier musique Batocera
|
||||
music_folder = "/userdata/roms/ports/RGSX/assets/music"
|
||||
music_files = [f for f in os.listdir(music_folder) if f.lower().endswith(('.ogg', '.mp3'))]
|
||||
current_music = None # Suivre la musique en cours
|
||||
loading_step = "none"
|
||||
|
||||
def play_random_music():
|
||||
# Fonction pour extraire le contenu d'un fichier ZIP
|
||||
def extract_zip(zip_path, dest_dir, url):
|
||||
"""Extrait le contenu du fichier ZIP dans le dossier cible avec un suivi progressif de la progression."""
|
||||
try:
|
||||
lock = threading.Lock()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
total_size = sum(info.file_size for info in zip_ref.infolist() if not info.is_dir())
|
||||
logger.info(f"Taille totale à extraire: {total_size} octets")
|
||||
if total_size == 0:
|
||||
logger.warning("ZIP vide ou ne contenant que des dossiers")
|
||||
return True, "ZIP vide extrait avec succès"
|
||||
|
||||
extracted_size = 0
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
chunk_size = 8192
|
||||
for info in zip_ref.infolist():
|
||||
if info.is_dir():
|
||||
continue
|
||||
file_path = os.path.join(dest_dir, info.filename)
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with zip_ref.open(info) as source, open(file_path, 'wb') as dest:
|
||||
file_size = info.file_size
|
||||
file_extracted = 0
|
||||
while True:
|
||||
chunk = source.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
dest.write(chunk)
|
||||
file_extracted += len(chunk)
|
||||
extracted_size += len(chunk)
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = extracted_size
|
||||
config.download_progress[url]["total_size"] = total_size
|
||||
config.download_progress[url]["status"] = "Extracting"
|
||||
config.download_progress[url]["progress_percent"] = (extracted_size / total_size * 100) if total_size > 0 else 0
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
# Logger une seule ligne à la fin de l'extraction du fichier
|
||||
progress_percentage = (extracted_size / total_size * 100) if total_size > 0 else 0
|
||||
logger.debug(f"Extraction terminée pour {info.filename}, file_extracted: {file_extracted}/{file_size}, total_extracted: {extracted_size}/{total_size}, progression: {progress_percentage:.1f}%")
|
||||
os.chmod(file_path, 0o644)
|
||||
|
||||
for root, dirs, _ in os.walk(dest_dir):
|
||||
for dir_name in dirs:
|
||||
os.chmod(os.path.join(root, dir_name), 0o755)
|
||||
|
||||
os.remove(zip_path)
|
||||
logger.info(f"Fichier ZIP {zip_path} extrait dans {dest_dir} et supprimé")
|
||||
return True, "ZIP extrait avec succès"
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'extraction de {zip_path}: {e}")
|
||||
return False, str(e)
|
||||
|
||||
# Fonction pour extraire le contenu d'un fichier RAR
|
||||
def extract_rar(rar_path, dest_dir, url):
|
||||
"""Extrait le contenu du fichier RAR dans le dossier cible, préservant la structure des dossiers."""
|
||||
try:
|
||||
lock = threading.Lock()
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
|
||||
result = subprocess.run(['unrar'], capture_output=True, text=True)
|
||||
if result.returncode not in [0, 1]:
|
||||
logger.error("Commande unrar non disponible")
|
||||
return False, "Commande unrar non disponible"
|
||||
|
||||
result = subprocess.run(['unrar', 'l', '-v', rar_path], capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr.strip()
|
||||
logger.error(f"Erreur lors de la liste des fichiers RAR: {error_msg}")
|
||||
return False, f"Échec de la liste des fichiers RAR: {error_msg}"
|
||||
|
||||
logger.debug(f"Sortie brute de 'unrar l -v {rar_path}':\n{result.stdout}")
|
||||
|
||||
total_size = 0
|
||||
files_to_extract = []
|
||||
root_dirs = set()
|
||||
lines = result.stdout.splitlines()
|
||||
in_file_list = False
|
||||
for line in lines:
|
||||
if line.startswith("----"):
|
||||
in_file_list = not in_file_list
|
||||
continue
|
||||
if in_file_list:
|
||||
match = re.match(r'^\s*(\S+)\s+(\d+)\s+\d*\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})\s+(.+)$', line)
|
||||
if match:
|
||||
attrs = match.group(1)
|
||||
file_size = int(match.group(2))
|
||||
file_date = match.group(3)
|
||||
file_name = match.group(4).strip()
|
||||
if 'D' not in attrs:
|
||||
files_to_extract.append((file_name, file_size))
|
||||
total_size += file_size
|
||||
root_dir = file_name.split('/')[0] if '/' in file_name else ''
|
||||
if root_dir:
|
||||
root_dirs.add(root_dir)
|
||||
logger.debug(f"Ligne parsée: {file_name}, taille: {file_size}, date: {file_date}")
|
||||
else:
|
||||
logger.debug(f"Dossier ignoré: {file_name}")
|
||||
else:
|
||||
logger.debug(f"Ligne ignorée (format inattendu): {line}")
|
||||
|
||||
logger.info(f"Taille totale à extraire (RAR): {total_size} octets")
|
||||
logger.debug(f"Fichiers à extraire: {files_to_extract}")
|
||||
logger.debug(f"Dossiers racines détectés: {root_dirs}")
|
||||
if total_size == 0:
|
||||
logger.warning("RAR vide, ne contenant que des dossiers, ou erreur de parsing")
|
||||
return False, "RAR vide ou erreur lors de la liste des fichiers"
|
||||
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = 0
|
||||
config.download_progress[url]["total_size"] = total_size
|
||||
config.download_progress[url]["status"] = "Extracting"
|
||||
config.download_progress[url]["progress_percent"] = 0
|
||||
config.needs_redraw = True
|
||||
|
||||
escaped_rar_path = rar_path.replace(" ", "\\ ")
|
||||
escaped_dest_dir = dest_dir.replace(" ", "\\ ")
|
||||
process = subprocess.Popen(['unrar', 'x', '-y', escaped_rar_path, escaped_dest_dir],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
if process.returncode != 0:
|
||||
logger.error(f"Erreur lors de l'extraction de {rar_path}: {stderr}")
|
||||
return False, f"Erreur lors de l'extraction: {stderr}"
|
||||
|
||||
extracted_size = 0
|
||||
extracted_files = []
|
||||
total_files = len(files_to_extract)
|
||||
for i, (expected_file, file_size) in enumerate(files_to_extract):
|
||||
file_path = os.path.join(dest_dir, expected_file)
|
||||
if os.path.exists(file_path):
|
||||
extracted_size += file_size
|
||||
extracted_files.append(expected_file)
|
||||
os.chmod(file_path, 0o644)
|
||||
logger.debug(f"Fichier extrait: {expected_file}, taille: {file_size}, chemin: {file_path}")
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = extracted_size
|
||||
config.download_progress[url]["status"] = "Extracting"
|
||||
config.download_progress[url]["progress_percent"] = ((i + 1) / total_files * 100) if total_files > 0 else 0
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
logger.warning(f"Fichier non trouvé après extraction: {expected_file}")
|
||||
|
||||
missing_files = [f for f, _ in files_to_extract if f not in extracted_files]
|
||||
if missing_files:
|
||||
logger.warning(f"Fichiers non extraits: {', '.join(missing_files)}")
|
||||
return False, f"Fichiers non extraits: {', '.join(missing_files)}"
|
||||
|
||||
if dest_dir == "/userdata/roms/ps3" and len(root_dirs) == 1:
|
||||
root_dir = root_dirs.pop()
|
||||
old_path = os.path.join(dest_dir, root_dir)
|
||||
new_path = os.path.join(dest_dir, f"{root_dir}.ps3")
|
||||
if os.path.isdir(old_path):
|
||||
try:
|
||||
os.rename(old_path, new_path)
|
||||
logger.info(f"Dossier renommé: {old_path} -> {new_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du renommage de {old_path} en {new_path}: {str(e)}")
|
||||
return False, f"Erreur lors du renommage du dossier: {str(e)}"
|
||||
else:
|
||||
logger.warning(f"Dossier racine {old_path} non trouvé après extraction")
|
||||
elif dest_dir == "/userdata/roms/ps3" and len(root_dirs) > 1:
|
||||
logger.warning(f"Plusieurs dossiers racines détectés dans l'archive: {root_dirs}. Aucun renommage effectué.")
|
||||
|
||||
for root, dirs, _ in os.walk(dest_dir):
|
||||
for dir_name in dirs:
|
||||
os.chmod(os.path.join(root, dir_name), 0o755)
|
||||
|
||||
os.remove(rar_path)
|
||||
logger.info(f"Fichier RAR {rar_path} extrait dans {dest_dir} et supprimé")
|
||||
return True, "RAR extrait avec succès"
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'extraction de {rar_path}: {str(e)}")
|
||||
return False, str(e)
|
||||
finally:
|
||||
if os.path.exists(rar_path):
|
||||
try:
|
||||
os.remove(rar_path)
|
||||
logger.info(f"Fichier RAR {rar_path} supprimé après échec de l'extraction")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la suppression de {rar_path}: {str(e)}")
|
||||
|
||||
def play_random_music(music_files, music_folder, current_music=None):
|
||||
"""Joue une musique aléatoire et configure l'événement de fin."""
|
||||
global current_music
|
||||
if music_files:
|
||||
# Éviter de rejouer la même musique consécutivement
|
||||
available_music = [f for f in music_files if f != current_music]
|
||||
@@ -285,14 +500,14 @@ def play_random_music():
|
||||
pygame.mixer.music.set_volume(0.5)
|
||||
pygame.mixer.music.play(loops=0) # Jouer une seule fois
|
||||
pygame.mixer.music.set_endevent(pygame.USEREVENT + 1) # Événement de fin
|
||||
current_music = music_file # Mettre à jour la musique en cours
|
||||
set_music_popup(music_file) # Afficher le nom de la musique dans la popup
|
||||
return music_file # Retourner la nouvelle musique pour mise à jour
|
||||
else:
|
||||
logger.debug("Aucune musique trouvée dans /userdata/roms/ports/RGSX/assets/music")
|
||||
return current_music
|
||||
|
||||
def set_music_popup(music_name):
|
||||
"""Définit le nom de la musique à afficher dans la popup."""
|
||||
global current_music_name, music_popup_start_time
|
||||
current_music_name = f"♬ {os.path.splitext(music_name)[0]}" # Utilise l'emoji ♬ directement
|
||||
music_popup_start_time = pygame.time.get_ticks() / 1000 # Temps actuel en secondes
|
||||
|
||||
music_popup_start_time = pygame.time.get_ticks() / 1000 # Temps actuel en secondes
|
||||
Reference in New Issue
Block a user