v1.9.7.7 - Corrrection de bugs de téléchargements, bug detection clé 1fichier, ajout options d'accessibilité pour agrandir/reduire la taille des polices

This commit is contained in:
skymike03
2025-07-27 18:58:10 +02:00
parent 6f2bf5eea8
commit ec321dfc7e
9 changed files with 509 additions and 256 deletions

View File

@@ -7,7 +7,7 @@ import logging
import requests
import queue
import datetime
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, draw_language_menu, THEME_COLORS
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_cancel_download_dialog, draw_confirm_dialog, draw_redownload_game_cache_dialog, draw_popup, draw_gradient, THEME_COLORS
from language import handle_language_menu_events, _
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates
from controls import handle_controls, validate_menu_state, process_key_repeats
@@ -38,8 +38,19 @@ logger = logging.getLogger(__name__)
pygame.init()
config.init_font()
pygame.joystick.init()
logger.debug("--------------------------------------------------------------------")
logger.debug("---------------------------DEBUT LOG--------------------------------")
logger.debug("--------------------------------------------------------------------")
# Chargement des paramètres d'accessibilité
from utils import load_accessibility_settings
config.accessibility_settings = load_accessibility_settings()
for i, scale in enumerate(config.font_scale_options):
if scale == config.accessibility_settings.get("font_scale", 1.0):
config.current_font_scale_index = i
break
# Chargement et initialisation de la langue
from language import initialize_language
initialize_language()
@@ -53,22 +64,8 @@ clock = pygame.time.Clock()
pygame.display.set_caption("RGSX")
# Initialisation des polices
try:
font_path = os.path.join(config.APP_FOLDER, "assets", "Pixel-UniCode.ttf")
config.font = pygame.font.Font(font_path, 36) # Police principale
config.title_font = pygame.font.Font(font_path, 48) # Police pour les titres
config.search_font = pygame.font.Font(font_path, 48) # Police pour la recherche
config.progress_font = pygame.font.Font(font_path, 36) # Police pour l'affichage de la progression
config.small_font = pygame.font.Font(font_path, 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")
# Initialisation des polices via config
config.init_font()
# Mise à jour de la résolution dans config
config.screen_width, config.screen_height = pygame.display.get_surface().get_size()
@@ -190,8 +187,10 @@ async def main():
events = pygame.event.get()
for event in events:
# Gestion directe des événements pour le menu de langue
if config.menu_state == "language_select" and event.type == pygame.KEYDOWN:
handle_language_menu_events(event, screen)
if config.menu_state == "language_select":
from language import handle_language_menu_events
if handle_language_menu_events(event, screen):
config.needs_redraw = True
continue
if event.type == pygame.USEREVENT + 1: # Événement de fin de musique
@@ -228,6 +227,12 @@ async def main():
#logger.debug(f"Événement transmis à handle_controls dans pause_menu: {event.type}")
continue
if config.menu_state == "accessibility_menu":
from accessibility import handle_accessibility_events
if handle_accessibility_events(event):
config.needs_redraw = True
continue
if config.menu_state == "controls_help":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
@@ -237,7 +242,11 @@ async def main():
if config.menu_state == "confirm_clear_history":
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 == "confirm_cancel_download":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
continue
if config.menu_state == "redownload_game_cache":
@@ -298,6 +307,7 @@ async def main():
"game_name": game_name,
"status": "downloading",
"progress": 0,
"message": _("download_initializing"),
"url": url,
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
@@ -559,10 +569,18 @@ async def main():
# logger.debug("Screen updated with draw_history_list")
elif config.menu_state == "confirm_clear_history":
draw_clear_history_dialog(screen)
elif config.menu_state == "confirm_cancel_download":
draw_cancel_download_dialog(screen)
elif config.menu_state == "redownload_game_cache":
draw_redownload_game_cache_dialog(screen)
elif config.menu_state == "restart_popup":
draw_popup(screen)
elif config.menu_state == "accessibility_menu":
from accessibility import draw_accessibility_menu
draw_accessibility_menu(screen)
elif config.menu_state == "language_select":
from display import draw_language_menu
draw_language_menu(screen)
else:
config.menu_state = "platform"
draw_platform_grid(screen)

129
accessibility.py Normal file
View File

@@ -0,0 +1,129 @@
import pygame #type:ignore
import config
from utils import save_accessibility_settings
from language import _
def draw_accessibility_menu(screen):
"""Affiche le menu d'accessibilité avec curseur pour la taille de police."""
from display import OVERLAY, THEME_COLORS, draw_stylized_button
screen.blit(OVERLAY, (0, 0))
# Titre
title_text = _("menu_accessibility")
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
title_rect = title_surface.get_rect(center=(config.screen_width // 2, config.screen_height // 4))
# Fond du titre
title_bg_rect = title_rect.inflate(40, 20)
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_bg_rect, border_radius=10)
pygame.draw.rect(screen, THEME_COLORS["border"], title_bg_rect, 2, border_radius=10)
screen.blit(title_surface, title_rect)
# Curseur de taille de police
current_scale = config.font_scale_options[config.current_font_scale_index]
font_text = _("accessibility_font_size").format(f"{current_scale:.1f}")
# Position du curseur
cursor_y = config.screen_height // 2
cursor_width = 400
cursor_height = 60
cursor_x = (config.screen_width - cursor_width) // 2
# Fond du curseur
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (cursor_x, cursor_y, cursor_width, cursor_height), border_radius=10)
pygame.draw.rect(screen, THEME_COLORS["border"], (cursor_x, cursor_y, cursor_width, cursor_height), 2, border_radius=10)
# Flèches gauche/droite
arrow_size = 30
left_arrow_x = cursor_x + 20
right_arrow_x = cursor_x + cursor_width - arrow_size - 20
arrow_y = cursor_y + (cursor_height - arrow_size) // 2
# Flèche gauche
left_color = THEME_COLORS["fond_lignes"] if config.current_font_scale_index > 0 else THEME_COLORS["border"]
pygame.draw.polygon(screen, left_color, [
(left_arrow_x + arrow_size, arrow_y),
(left_arrow_x, arrow_y + arrow_size // 2),
(left_arrow_x + arrow_size, arrow_y + arrow_size)
])
# Flèche droite
right_color = THEME_COLORS["fond_lignes"] if config.current_font_scale_index < len(config.font_scale_options) - 1 else THEME_COLORS["border"]
pygame.draw.polygon(screen, right_color, [
(right_arrow_x, arrow_y),
(right_arrow_x + arrow_size, arrow_y + arrow_size // 2),
(right_arrow_x, arrow_y + arrow_size)
])
# Texte au centre
text_surface = config.font.render(font_text, True, THEME_COLORS["text"])
text_rect = text_surface.get_rect(center=(cursor_x + cursor_width // 2, cursor_y + cursor_height // 2))
screen.blit(text_surface, text_rect)
# Instructions
instruction_text = _("language_select_instruction")
instruction_surface = config.small_font.render(instruction_text, True, THEME_COLORS["text"])
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, config.screen_height - 100))
screen.blit(instruction_surface, instruction_rect)
def handle_accessibility_events(event):
"""Gère les événements du menu d'accessibilité avec support clavier et manette."""
# Gestion des touches du clavier
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT and config.current_font_scale_index > 0:
config.current_font_scale_index -= 1
update_font_scale()
return True
elif event.key == pygame.K_RIGHT and config.current_font_scale_index < len(config.font_scale_options) - 1:
config.current_font_scale_index += 1
update_font_scale()
return True
elif event.key == pygame.K_RETURN or event.key == pygame.K_ESCAPE:
config.menu_state = "pause_menu"
return True
# Gestion des boutons de la manette
elif event.type == pygame.JOYBUTTONDOWN:
if event.button == 0: # Bouton A (valider)
config.menu_state = "pause_menu"
return True
elif event.button == 1: # Bouton B (annuler)
config.menu_state = "pause_menu"
return True
# Gestion du D-pad
elif event.type == pygame.JOYHATMOTION:
if event.value == (-1, 0): # Gauche
if config.current_font_scale_index > 0:
config.current_font_scale_index -= 1
update_font_scale()
return True
elif event.value == (1, 0): # Droite
if config.current_font_scale_index < len(config.font_scale_options) - 1:
config.current_font_scale_index += 1
update_font_scale()
return True
# Gestion du joystick analogique (axe horizontal)
elif event.type == pygame.JOYAXISMOTION:
if event.axis == 0 and abs(event.value) > 0.5: # Joystick gauche horizontal
if event.value < -0.5 and config.current_font_scale_index > 0: # Gauche
config.current_font_scale_index -= 1
update_font_scale()
return True
elif event.value > 0.5 and config.current_font_scale_index < len(config.font_scale_options) - 1: # Droite
config.current_font_scale_index += 1
update_font_scale()
return True
return False
def update_font_scale():
"""Met à jour l'échelle de police et sauvegarde."""
new_scale = config.font_scale_options[config.current_font_scale_index]
config.accessibility_settings["font_scale"] = new_scale
save_accessibility_settings(config.accessibility_settings)
# Réinitialiser les polices
config.init_font()
config.needs_redraw = True

View File

@@ -3,7 +3,7 @@ import os
import logging
# Version actuelle de l'application
app_version = "1.9.7.6"
app_version = "1.9.7.7"
@@ -43,6 +43,9 @@ REPEAT_ACTION_DEBOUNCE = 150 # Délai anti-rebond pour répétitions (ms) - aug
platforms = []
current_platform = 0
accessibility_mode = False # Mode accessibilité pour les polices agrandies
accessibility_settings = {"font_scale": 1.0}
font_scale_options = [0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
current_font_scale_index = 3 # Index pour 1.0
platform_names = {} # {platform_id: platform_name}
games = []
current_game = 0
@@ -89,6 +92,7 @@ current_history_item = 0
history_scroll_offset = 0 # Offset pour le défilement de l'historique
visible_history_items = 15 # Nombre d'éléments d'historique visibles (ajusté dynamiquement)
confirm_clear_selection = 0 # confirmation clear historique
confirm_cancel_selection = 0 # confirmation annulation téléchargement
last_state_change_time = 0 # Temps du dernier changement d'état pour debounce
debounce_delay = 200 # Délai de debounce en millisecondes
platform_dicts = [] # Liste des dictionnaires de plateformes
@@ -126,27 +130,32 @@ small_font = None
def init_font():
"""Initialise les polices après pygame.init()."""
logger.debug("--------------------------------------------------------------------")
logger.debug("---------------------------DEBUT LOG--------------------------------")
logger.debug("--------------------------------------------------------------------")
global FONT, progress_font, title_font, search_font, small_font
multiplier = 1.5 if accessibility_mode else 1.0
global font, progress_font, title_font, search_font, small_font
font_scale = accessibility_settings.get("font_scale", 1.0)
try:
FONT = pygame.font.Font(None, int(36 * multiplier))
progress_font = pygame.font.Font(None, int(28 * multiplier))
title_font = pygame.font.Font(None, int(48 * multiplier))
search_font = pygame.font.Font(None, int(36 * multiplier))
small_font = pygame.font.Font(None, int(24 * multiplier))
logger.debug(f"Polices initialisées avec succès (mode accessibilité: {accessibility_mode})")
# amazonq-ignore-next-line
except pygame.error as e:
logger.error(f"Erreur lors de l'initialisation des polices : {e}")
FONT = None
progress_font = None
title_font = None
search_font = None
small_font = None
font_path = os.path.join(APP_FOLDER, "assets", "Pixel-UniCode.ttf")
font = pygame.font.Font(font_path, int(36 * font_scale))
title_font = pygame.font.Font(font_path, int(48 * font_scale))
search_font = pygame.font.Font(font_path, int(48 * font_scale))
progress_font = pygame.font.Font(font_path, int(36 * font_scale))
small_font = pygame.font.Font(font_path, int(28 * font_scale))
logger.debug(f"Polices Pixel-UniCode initialisées (font_scale: {font_scale})")
except Exception as e:
try:
font = pygame.font.SysFont("arial", int(48 * font_scale))
title_font = pygame.font.SysFont("arial", int(60 * font_scale))
search_font = pygame.font.SysFont("arial", int(60 * font_scale))
progress_font = pygame.font.SysFont("arial", int(36 * font_scale))
small_font = pygame.font.SysFont("arial", int(28 * font_scale))
logger.debug(f"Polices Arial initialisées (font_scale: {font_scale})")
except Exception as e2:
logger.error(f"Erreur lors de l'initialisation des polices : {e2}")
font = None
progress_font = None
title_font = None
search_font = None
small_font = None
def validate_resolution():
"""Valide la résolution de l'écran par rapport aux capacités de l'écran."""

View File

@@ -9,7 +9,7 @@ import json
import os
from display import draw_validation_transition
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 utils import load_games, check_extension_before_download, is_extension_supported, load_extensions_json, sanitize_filename, load_api_key_1fichier
from history import load_history, clear_history, add_to_history, save_history
import logging
from language import _ # Import de la fonction de traduction
@@ -504,6 +504,7 @@ def handle_controls(event, sources, joystick, screen):
config.current_history_item = len(config.history) - 1
# Vérifier d'abord si c'est un lien 1fichier
if is_1fichier_url(url):
config.API_KEY_1FICHIER = load_api_key_1fichier()
if not config.API_KEY_1FICHIER:
config.previous_menu_state = config.menu_state
config.menu_state = "error"
@@ -739,13 +740,50 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
logger.error(f"config.pending_download est None pour {game_name}")
break
elif is_input_matched(event, "cancel"):
elif is_input_matched(event, "cancel") or is_input_matched(event, "history"):
if config.history and config.current_history_item < len(config.history):
entry = config.history[config.current_history_item]
if entry.get("status") in ["downloading", "Téléchargement", "Extracting"] and is_input_matched(event, "cancel"):
config.menu_state = "confirm_cancel_download"
config.confirm_cancel_selection = 0
config.needs_redraw = True
logger.debug("Demande d'annulation de téléchargement")
return action
config.menu_state = validate_menu_state(config.previous_menu_state)
config.current_history_item = 0
config.history_scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Retour à {config.menu_state} depuis history")
# Confirmation annulation téléchargement
elif config.menu_state == "confirm_cancel_download":
if is_input_matched(event, "confirm"):
if config.confirm_cancel_selection == 1: # Oui
entry = config.history[config.current_history_item]
url = entry.get("url")
# Annuler la tâche correspondante
for task_id, (task, task_url, game_name, platform) in list(config.download_tasks.items()):
if task_url == url:
task.cancel()
del config.download_tasks[task_id]
entry["status"] = "Canceled"
entry["progress"] = 0
entry["message"] = "Téléchargement annulé"
save_history(config.history)
logger.debug(f"Téléchargement annulé: {game_name}")
break
config.menu_state = "history"
config.needs_redraw = True
else: # Non
config.menu_state = "history"
config.needs_redraw = True
elif is_input_matched(event, "left") or is_input_matched(event, "right"):
config.confirm_cancel_selection = 1 - config.confirm_cancel_selection
config.needs_redraw = True
elif is_input_matched(event, "cancel"):
config.menu_state = "history"
config.needs_redraw = True
# Confirmation vider l'historique"
elif config.menu_state == "confirm_clear_history":
logger.debug(f"État confirm_clear_history, confirm_clear_selection={config.confirm_clear_selection}, événement={event.type}, valeur={getattr(event, 'value', None)}")
@@ -866,26 +904,10 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
logger.debug(f"Passage à language_select depuis pause_menu")
elif config.selected_option == 4: # Accessibility
config.accessibility_mode = not config.accessibility_mode
config.init_font()
# Reinitialiser les polices dans le main
font_path = os.path.join(config.APP_FOLDER, "assets", "Pixel-UniCode.ttf")
multiplier = 1.5 if config.accessibility_mode else 1.0
try:
config.font = pygame.font.Font(font_path, int(36 * multiplier))
config.title_font = pygame.font.Font(font_path, int(48 * multiplier))
config.search_font = pygame.font.Font(font_path, int(48 * multiplier))
config.progress_font = pygame.font.Font(font_path, int(36 * multiplier))
config.small_font = pygame.font.Font(font_path, int(28 * multiplier))
except:
config.font = pygame.font.SysFont("arial", int(48 * multiplier))
config.title_font = pygame.font.SysFont("arial", int(60 * multiplier))
config.search_font = pygame.font.SysFont("arial", int(60 * multiplier))
config.progress_font = pygame.font.SysFont("arial", int(36 * multiplier))
config.small_font = pygame.font.SysFont("arial", int(28 * multiplier))
config.menu_state = config.previous_menu_state
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "accessibility_menu"
config.needs_redraw = True
logger.debug(f"Mode accessibilité {'activé' if config.accessibility_mode else 'désactivé'}")
logger.debug("Passage au menu accessibilité")
elif config.selected_option == 5: # Redownload game cache
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "redownload_game_cache"

View File

@@ -530,7 +530,7 @@ def draw_game_list(screen):
for i in range(config.scroll_offset, min(config.scroll_offset + items_per_page, len(games))):
game_name = games[i][0] if isinstance(games[i], (list, tuple)) else games[i]
color = THEME_COLORS["fond_lignes"] if i == config.current_game else THEME_COLORS["text"]
game_text = truncate_text_middle(game_name, config.small_font, rect_width - 40)
game_text = truncate_text_middle(game_name, config.small_font, rect_width - 40, is_filename=False)
text_surface = config.small_font.render(game_text, True, color)
text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + (i - config.scroll_offset) * line_height + line_height // 2))
if i == config.current_game:
@@ -680,7 +680,8 @@ def draw_history_list(screen):
# logger.debug(f"Affichage terminé: {game_name}, status={status_text}")
elif status == "Erreur":
status_text = _("history_status_error").format(entry.get('message', 'Échec'))
#logger.debug(f"Affichage erreur: {game_name}, status={status_text}")
elif status == "Canceled":
status_text = _("history_status_canceled")
else:
status_text = status
#logger.debug(f"Affichage statut inconnu: {game_name}, status={status_text}")
@@ -757,8 +758,37 @@ def draw_clear_history_dialog(screen):
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
screen.blit(text, text_rect)
draw_stylized_button(screen, _("button_yes"), rect_x + rect_width // 2 - 180, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.confirm_clear_selection == 1)
draw_stylized_button(screen, _("button_no"), rect_x + rect_width // 2 + 20, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.confirm_clear_selection == 0)
button_width = min(160, (rect_width - 60) // 2)
draw_stylized_button(screen, _("button_yes"), rect_x + rect_width // 2 - button_width - 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_clear_selection == 1)
draw_stylized_button(screen, _("button_no"), rect_x + rect_width // 2 + 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_clear_selection == 0)
def draw_cancel_download_dialog(screen):
"""Affiche la boîte de dialogue de confirmation pour annuler un téléchargement."""
screen.blit(OVERLAY, (0, 0))
message = _("confirm_cancel_download")
wrapped_message = wrap_text(message, config.font, config.screen_width - 80)
line_height = config.font.get_height() + 5
text_height = len(wrapped_message) * line_height
button_height = int(config.screen_height * 0.0463)
margin_top_bottom = 20
rect_height = text_height + button_height + 2 * margin_top_bottom
max_text_width = max([config.font.size(line)[0] for line in wrapped_message], default=300)
rect_width = max_text_width + 150
rect_x = (config.screen_width - rect_width) // 2
rect_y = (config.screen_height - rect_height) // 2
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
for i, line in enumerate(wrapped_message):
text = config.font.render(line, True, THEME_COLORS["text"])
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
screen.blit(text, text_rect)
button_width = min(160, (rect_width - 60) // 2)
draw_stylized_button(screen, _("button_yes"), rect_x + rect_width // 2 - button_width - 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_cancel_selection == 1)
draw_stylized_button(screen, _("button_no"), rect_x + rect_width // 2 + 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_cancel_selection == 0)
# Affichage du clavier virtuel sur non-PC
def draw_virtual_keyboard(screen):
@@ -960,7 +990,6 @@ def draw_controls(screen, menu_state, current_music_name=None, music_popup_start
current_time = pygame.time.get_ticks() / 1000
if current_time - config.music_popup_start_time < 3.0: # Afficher pendant 3 secondes
control_text += f" | {config.current_music_name}"
logger.debug(f"config.current_music_name")
max_width = config.screen_width - 40
wrapped_controls = wrap_text(control_text, config.small_font, max_width)
line_height = config.small_font.get_height() + 5
@@ -1124,22 +1153,30 @@ def draw_controls_help(screen, previous_state):
}
# Catégories de contrôles
nav_text = _("controls_navigation")
pages_text = _("controls_pages")
confirm_select_text = _("controls_confirm_select")
cancel_back_text = _("controls_cancel_back")
history_text = _("controls_history")
clear_history_text = _("controls_clear_history")
filter_search_text = _("controls_filter_search")
control_categories = {
"Navigation": [
f"{get_control_display('up', '')} {get_control_display('down', '')} {get_control_display('left', '')} {get_control_display('right', '')} : Navigation",
f"{get_control_display('page_up', 'LB')} {get_control_display('page_down', 'RB')} : Pages"
_("controls_category_navigation"): [
f"{get_control_display('up', '')} {get_control_display('down', '')} {get_control_display('left', '')} {get_control_display('right', '')} : {nav_text}",
f"{get_control_display('page_up', 'LB')} {get_control_display('page_down', 'RB')} : {pages_text}"
],
"Actions principales": [
f"{get_control_display('confirm', 'A')} : Confirmer/Sélectionner",
f"{get_control_display('cancel', 'B')} : Annuler/Retour",
_("controls_category_main_actions"): [
f"{get_control_display('confirm', 'A')} : {confirm_select_text}",
f"{get_control_display('cancel', 'B')} : {cancel_back_text}",
f"{get_control_display('start', 'Start')} : {start_text}"
],
"Téléchargements": [
f"{get_control_display('history', 'Y')} : Historique",
f"{get_control_display('progress', 'X')} : Effacer historique"
_("controls_category_downloads"): [
f"{get_control_display('history', 'Y')} : {history_text}",
f"{get_control_display('progress', 'X')} : {clear_history_text}"
],
"Recherche": [
f"{get_control_display('filter', 'Select')} : Filtrer/Rechercher",
_("controls_category_search"): [
f"{get_control_display('filter', 'Select')} : {filter_search_text}",
f"{get_control_display('delete', 'Suppr')} : {delete_text}",
f"{get_control_display('space', 'Espace')} : {space_text}"
]
@@ -1185,7 +1222,7 @@ def draw_controls_help(screen, previous_state):
pygame.draw.rect(screen, THEME_COLORS["border"], (popup_x, popup_y, popup_width, popup_height), 2, border_radius=12)
# Titre
title_text = "Aide des contrôles"
title_text = _("controls_help_title")
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
title_rect = title_surface.get_rect(center=(config.screen_width // 2, popup_y + 25))
screen.blit(title_surface, title_rect)
@@ -1257,8 +1294,9 @@ def draw_confirm_dialog(screen):
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
screen.blit(text, text_rect)
draw_stylized_button(screen, _("button_yes"), rect_x + rect_width // 2 - 180, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.confirm_selection == 1)
draw_stylized_button(screen, _("button_no"), rect_x + rect_width // 2 + 20, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.confirm_selection == 0)
button_width = min(160, (rect_width - 60) // 2)
draw_stylized_button(screen, _("button_yes"), rect_x + rect_width // 2 - button_width - 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_selection == 1)
draw_stylized_button(screen, _("button_no"), rect_x + rect_width // 2 + 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_selection == 0)
# draw_redownload_game_cache_dialog
def draw_redownload_game_cache_dialog(screen):

View File

@@ -42,6 +42,7 @@
"history_status_extracting": "Extracting: {0}%",
"history_status_completed": "Completed",
"history_status_error": "Error: {0}",
"history_status_canceled": "Canceled",
"download_status": "{0}: {1}",
"download_progress": "{0}% {1} MB / {2} MB",
@@ -66,6 +67,7 @@
"menu_remap_controls": "Remap controls",
"menu_history": "History",
"menu_language": "Language",
"menu_accessibility": "Accessibility",
"menu_redownload_cache": "Redownload Games cache",
"menu_quit": "Quit",
@@ -155,5 +157,20 @@
"utils_permission_denied": "Permission denied during extraction: {0}",
"utils_extraction_failed": "Extraction failed: {0}",
"utils_unrar_unavailable": "Unrar command not available",
"utils_rar_list_failed": "Failed to list RAR files: {0}"
"utils_rar_list_failed": "Failed to list RAR files: {0}",
"download_initializing": "Initializing...",
"accessibility_font_size": "Font size: {0}",
"confirm_cancel_download": "Cancel current download?",
"controls_help_title": "Controls Help",
"controls_category_navigation": "Navigation",
"controls_category_main_actions": "Main Actions",
"controls_category_downloads": "Downloads",
"controls_category_search": "Search",
"controls_navigation": "Navigation",
"controls_pages": "Pages",
"controls_confirm_select": "Confirm/Select",
"controls_cancel_back": "Cancel/Back",
"controls_history": "History",
"controls_clear_history": "Clear History",
"controls_filter_search": "Filter/Search"
}

View File

@@ -42,6 +42,7 @@
"history_status_extracting": "Extraction : {0}%",
"history_status_completed": "Terminé",
"history_status_error": "Erreur : {0}",
"history_status_canceled": "Annulé",
"download_status": "{0} : {1}",
"download_progress": "{0}% {1} Mo / {2} Mo",
@@ -66,6 +67,7 @@
"menu_remap_controls": "Remapper les contrôles",
"menu_history": "Historique",
"menu_language": "Langue",
"menu_accessibility": "Accessibilité",
"menu_redownload_cache": "Retélécharger le cache des jeux",
"menu_quit": "Quitter",
@@ -144,7 +146,22 @@
"network_permission_error": "Pas de permission d'écriture dans {0}",
"network_file_not_found": "Le fichier {0} n'existe pas",
"network_cannot_get_filename": "Impossible de récupérer le nom du fichier",
"network_cannot_get_download_url": "Impossible de récupérer l'URL de téléchargement",
"network_cannot_get_download_url": "Impossible de récupérer l'URL de téléchargement",
"download_initializing": "Initialisation en cours...",
"accessibility_font_size": "Taille de police : {0}",
"confirm_cancel_download": "Annuler le téléchargement en cours ?",
"controls_help_title": "Aide des contrôles",
"controls_category_navigation": "Navigation",
"controls_category_main_actions": "Actions principales",
"controls_category_downloads": "Téléchargements",
"controls_category_search": "Recherche",
"controls_navigation": "Navigation",
"controls_pages": "Pages",
"controls_confirm_select": "Confirmer/Sélectionner",
"controls_cancel_back": "Annuler/Retour",
"controls_history": "Historique",
"controls_clear_history": "Effacer historique",
"controls_filter_search": "Filtrer/Rechercher",
"network_download_failed": "Échec du téléchargement après {0} tentatives",
"network_api_error": "Erreur lors de la requête API, la clé est peut-être incorrecte: {0}",
"network_download_error": "Erreur téléchargement {0}: {1}",

View File

@@ -10,6 +10,7 @@ from config import OTA_VERSION_ENDPOINT,APP_FOLDER, UPDATE_FOLDER, OTA_UPDATE_ZI
from utils import sanitize_filename, extract_zip, extract_rar, load_api_key_1fichier
from history import save_history
import logging
import datetime
import queue
import time
import os
@@ -152,9 +153,8 @@ def extract_update(zip_path, dest_dir, source_url):
logger.error(f"Erreur critique lors de l'extraction du ZIP {source_url}: {str(e)}")
return False, _("network_zip_extraction_error").format(source_url, str(e))
# File d'attente pour la progression
import queue
progress_queue = queue.Queue()
# File d'attente pour la progression - une par tâche
progress_queues = {}
@@ -162,13 +162,9 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
logger.debug(f"Début téléchargement: {game_name} depuis {url}, is_zip_non_supported={is_zip_non_supported}, task_id={task_id}")
result = [None, None]
# Vider la file d'attente avant de commencer
while not progress_queue.empty():
try:
progress_queue.get_nowait()
logger.debug(f"File progress_queue vidée pour {game_name}")
except queue.Empty:
break
# Créer une queue spécifique pour cette tâche
if task_id not in progress_queues:
progress_queues[task_id] = queue.Queue()
def download_thread():
logger.debug(f"Thread téléchargement démarré pour {url}, task_id={task_id}")
@@ -178,7 +174,6 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
if platform_dict["platform"] == platform:
dest_dir = os.path.join(config.ROMS_FOLDER, platform_dict.get("folder", platform.lower().replace(" ", "")))
logger.debug(f"Répertoire de destination trouvé pour {platform}: {dest_dir}")
#dest_dir = platform_dict.get("folder")
break
if not dest_dir:
dest_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), platform.lower().replace(" ", ""))
@@ -201,47 +196,27 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
'Upgrade-Insecure-Requests': '1'
}
# Utiliser une session pour gérer les cookies
session = requests.Session()
session.headers.update(headers)
# Première requête HEAD pour obtenir la vraie URL
#logger.debug(f"Première requête HEAD vers {url}")
head_response = session.head(url, timeout=30, allow_redirects=False)
#logger.debug(f"HEAD Status: {head_response.status_code}, Headers: {dict(head_response.headers)}")
# Suivre la redirection manuellement si nécessaire
final_url = url
if head_response.status_code in [301, 302, 303, 307, 308]:
final_url = head_response.headers.get('Location', url)
#logger.debug(f"Redirection détectée vers: {final_url}")
# Requête GET vers l'URL finale avec en-têtes spécifiques
download_headers = headers.copy()
download_headers['Accept'] = 'application/octet-stream, */*'
download_headers['Referer'] = 'https://myrient.erista.me/'
response = session.get(final_url, stream=True, timeout=30, allow_redirects=False, headers=download_headers)
response = session.get(url, stream=True, timeout=30, allow_redirects=True, headers=download_headers)
logger.debug(f"Status code: {response.status_code}")
logger.debug(f"Headers: {dict(response.headers)}")
response.raise_for_status()
total_size = int(response.headers.get('content-length', 0))
logger.debug(f"Taille totale: {total_size} octets")
if total_size == 0:
logger.warning(f"Taille de fichier 0, possible redirection ou erreur. URL finale: {response.url}")
# Vérifier si c'est une redirection
if response.url != url:
logger.debug(f"Redirection détectée: {url} -> {response.url}")
# Initialiser la progression avec task_id
progress_queue.put((task_id, 0, total_size))
progress_queues[task_id].put((task_id, 0, total_size))
logger.debug(f"Progression initiale envoyée: 0% pour {game_name}, task_id={task_id}")
downloaded = 0
chunk_size = 4096
last_update_time = time.time()
update_interval = 0.5 # Mettre à jour toutes les 0,5 secondes
update_interval = 0.1 # Mettre à jour toutes les 0,1 secondes
with open(dest_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=chunk_size):
if chunk:
@@ -250,24 +225,17 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
downloaded += size_received
current_time = time.time()
if current_time - last_update_time >= update_interval:
# Calculer le pourcentage correctement et le limiter entre 0 et 100
progress_percent = int(downloaded / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
progress_queue.put((task_id, downloaded, total_size))
progress_queues[task_id].put((task_id, downloaded, total_size))
last_update_time = current_time
else:
logger.debug("Chunk vide reçu")
os.chmod(dest_path, 0o644)
logger.debug(f"Téléchargement terminé: {dest_path}")
# Vérifier si l'extraction est nécessaire pour les archives non supportées
if is_zip_non_supported:
logger.debug(f"Extraction automatique nécessaire pour {dest_path}")
extension = os.path.splitext(dest_path)[1].lower()
if extension == ".zip":
try:
# Mettre à jour le statut avant l'extraction
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
@@ -319,7 +287,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
result[1] = _("network_download_error").format(game_name, str(e))
finally:
logger.debug(f"Thread téléchargement terminé pour {url}, task_id={task_id}")
progress_queue.put((task_id, result[0], result[1]))
progress_queues[task_id].put((task_id, result[0], result[1]))
logger.debug(f"Final result sent to queue: success={result[0]}, message={result[1]}, task_id={task_id}")
thread = threading.Thread(target=download_thread)
@@ -328,68 +296,63 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
# Boucle principale pour mettre à jour la progression
while thread.is_alive():
try:
while not progress_queue.empty():
data = progress_queue.get()
logger.debug(f"Progress queue data received: {data}")
if len(data) != 3 or data[0] != task_id: # Ignorer les données d'une autre tâche
logger.debug(f"Ignoring queue data for task_id={data[0]}, expected={task_id}")
continue
if isinstance(data[1], bool): # Fin du téléchargement
success, message = data[1], data[2]
# Vérifier si config.history est une liste avant d'itérer
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
# Utiliser une variable intermédiaire pour stocker le message
message_text = message
entry["message"] = message_text
save_history(config.history)
config.needs_redraw = True
logger.debug(f"Final update in history: status={entry['status']}, progress={entry['progress']}%, message={message}, task_id={task_id}")
break
else:
downloaded, total_size = data[1], data[2]
# Calculer le pourcentage correctement et le limiter entre 0 et 100
progress_percent = int(downloaded / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
# Vérifier si config.history est une liste avant d'itérer
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["progress"] = progress_percent
entry["status"] = "Téléchargement"
entry["downloaded_size"] = downloaded
entry["total_size"] = total_size
config.needs_redraw = True
break
await asyncio.sleep(0.2)
task_queue = progress_queues.get(task_id)
if task_queue:
while not task_queue.empty():
data = task_queue.get()
#logger.debug(f"Progress queue data received: {data}")
if isinstance(data[1], bool): # Fin du téléchargement
success, message = data[1], data[2]
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
save_history(config.history)
config.needs_redraw = True
logger.debug(f"Final update in history: status={entry['status']}, progress={entry['progress']}%, message={message}, task_id={task_id}")
break
else:
downloaded, total_size = data[1], data[2]
progress_percent = int(downloaded / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["progress"] = progress_percent
entry["status"] = "Téléchargement"
entry["downloaded_size"] = downloaded
entry["total_size"] = total_size
config.needs_redraw = True
break
await asyncio.sleep(0.1)
except Exception as e:
logger.error(f"Erreur mise à jour progression: {str(e)}")
thread.join()
#logger.debug(f"Thread joined for {url}, task_id={task_id}")
# Nettoyer la queue
if task_id in progress_queues:
del progress_queues[task_id]
return result[0], result[1]
async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False, task_id=None):
load_api_key_1fichier()
config.API_KEY_1FICHIER = load_api_key_1fichier()
logger.debug(f"Début téléchargement 1fichier: {game_name} depuis {url}, is_zip_non_supported={is_zip_non_supported}, task_id={task_id}")
logger.debug(f"Clé API 1fichier: {'présente' if config.API_KEY_1FICHIER else 'absente'}")
result = [None, None]
# Vider la file d'attente avant de commencer
while not progress_queue.empty():
try:
progress_queue.get_nowait()
logger.debug(f"File progress_queue vidée pour {game_name}")
except queue.Empty:
break
# Créer une queue spécifique pour cette tâche
logger.debug(f"Création queue pour task_id={task_id}")
if task_id not in progress_queues:
progress_queues[task_id] = queue.Queue()
def download_thread():
logger.debug(f"Thread téléchargement 1fichier démarré pour {url}, task_id={task_id}")
try:
link = url.split('&af=')[0]
logger.debug(f"URL nettoyée: {link}")
dest_dir = None
for platform_dict in config.platform_dicts:
if platform_dict["platform"] == platform:
@@ -398,10 +361,13 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
if not dest_dir:
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
dest_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), platform)
logger.debug(f"Répertoire destination déterminé: {dest_dir}")
logger.debug(f"Vérification répertoire destination: {dest_dir}")
os.makedirs(dest_dir, exist_ok=True)
logger.debug(f"Répertoire créé ou existant: {dest_dir}")
if not os.access(dest_dir, os.W_OK):
logger.error(f"Pas de permission d'écriture dans {dest_dir}")
raise PermissionError(f"Pas de permission d'écriture dans {dest_dir}")
headers = {
@@ -412,10 +378,9 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
"url": link,
"pretty": 1
}
#logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/file/info.cgi pour {url}")
logger.debug(f"Préparation requête file/info pour {link}")
response = requests.post("https://api.1fichier.com/v1/file/info.cgi", headers=headers, json=payload, timeout=30)
#logger.debug(f"Réponse reçue, status: {response.status_code}")
logger.debug(f"Réponse file/info reçue, code: {response.status_code}")
response.raise_for_status()
file_info = response.json()
@@ -427,7 +392,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
filename = file_info.get("filename", "").strip()
if not filename:
logger.error("Impossible de récupérer le nom du fichier")
logger.error(f"Impossible de récupérer le nom du fichier")
result[0] = False
result[1] = _("network_cannot_get_filename")
return
@@ -435,50 +400,48 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
sanitized_filename = sanitize_filename(filename)
dest_path = os.path.join(dest_dir, sanitized_filename)
logger.debug(f"Chemin destination: {dest_path}")
#logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/download/get_token.cgi pour {url}")
logger.debug(f"Envoi requête get_token pour {link}")
response = requests.post("https://api.1fichier.com/v1/download/get_token.cgi", headers=headers, json=payload, timeout=30)
#logger.debug(f"Réponse reçue, status: {response.status_code}")
logger.debug(f"Réponse get_token reçue, code: {response.status_code}")
response.raise_for_status()
download_info = response.json()
final_url = download_info.get("url")
if not final_url:
logger.error("Impossible de récupérer l'URL de téléchargement")
logger.error(f"Impossible de récupérer l'URL de téléchargement")
result[0] = False
result[1] = _("network_cannot_get_download_url")
return
logger.debug(f"URL de téléchargement obtenue: {final_url}")
lock = threading.Lock()
retries = 10
retry_delay = 10
# Initialiser la progression avec task_id
progress_queue.put((task_id, 0, 0)) # Taille initiale inconnue
#logger.debug(f"Progression initiale envoyée: 0% pour {game_name}, task_id={task_id}")
logger.debug(f"Initialisation progression avec taille inconnue pour task_id={task_id}")
progress_queues[task_id].put((task_id, 0, 0)) # Taille initiale inconnue
for attempt in range(retries):
logger.debug(f"Début tentative {attempt + 1} pour télécharger {final_url}")
try:
#logger.debug(f"Tentative {attempt + 1} : Envoi requête GET à {final_url}")
with requests.get(final_url, stream=True, headers={'User-Agent': 'Mozilla/5.0'}, timeout=30) as response:
#logger.debug(f"Réponse reçue, status: {response.status_code}")
logger.debug(f"Réponse GET reçue, code: {response.status_code}")
response.raise_for_status()
total_size = int(response.headers.get('content-length', 0))
logger.debug(f"Taille totale: {total_size} octets")
with lock:
# Vérifier si config.history est une liste avant d'itérer
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
entry["total_size"] = total_size
config.needs_redraw = True
break
progress_queue.put((task_id, 0, total_size)) # Mettre à jour la taille totale
progress_queues[task_id].put((task_id, 0, total_size)) # Mettre à jour la taille totale
downloaded = 0
chunk_size = 8192
last_update_time = time.time()
update_interval = 0.5 # Mettre à jour toutes les 0,5 secondes
update_interval = 0.1 # Mettre à jour toutes les 0,1 secondes
logger.debug(f"Ouverture fichier: {dest_path}")
with open(dest_path, 'wb') as f:
logger.debug(f"Ouverture fichier: {dest_path}")
for chunk in response.iter_content(chunk_size=chunk_size):
if chunk:
f.write(chunk)
@@ -486,11 +449,9 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
current_time = time.time()
if current_time - last_update_time >= update_interval:
with lock:
# Vérifier si config.history est une liste avant d'itérer
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
# Calculer le pourcentage correctement et le limiter entre 0 et 100
progress_percent = int(downloaded / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
entry["progress"] = progress_percent
@@ -498,14 +459,12 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
entry["downloaded_size"] = downloaded
entry["total_size"] = total_size
config.needs_redraw = True
#logger.debug(f"Progression mise à jour: {entry['progress']:.1f}% pour {game_name}")
break
progress_queue.put((task_id, downloaded, total_size))
progress_queues[task_id].put((task_id, downloaded, total_size))
last_update_time = current_time
if is_zip_non_supported:
with lock:
# Vérifier si config.history est une liste avant d'itérer
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] == "Téléchargement":
@@ -514,11 +473,12 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
config.needs_redraw = True
break
extension = os.path.splitext(dest_path)[1].lower()
logger.debug(f"Début extraction, type d'archive: {extension}")
if extension == ".zip":
try:
success, msg = extract_zip(dest_path, dest_dir, url)
logger.debug(f"Extraction ZIP terminée: {msg}")
if success:
logger.debug(f"Extraction ZIP réussie: {msg}")
result[0] = True
result[1] = _("network_download_extract_ok").format(game_name)
else:
@@ -526,14 +486,14 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
result[0] = False
result[1] = _("network_extraction_failed").format(msg)
except Exception as e:
logger.error(f"Exception lors de l'extraction: {str(e)}")
logger.error(f"Exception lors de l'extraction ZIP: {str(e)}")
result[0] = False
result[1] = f"Erreur téléchargement {game_name}: {str(e)}"
elif extension == ".rar":
try:
success, msg = extract_rar(dest_path, dest_dir, url)
logger.debug(f"Extraction RAR terminée: {msg}")
if success:
logger.debug(f"Extraction RAR réussie: {msg}")
result[0] = True
result[1] = _("network_download_extract_ok").format(game_name)
else:
@@ -549,6 +509,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
result[0] = True
result[1] = _("network_download_ok").format(game_name)
else:
logger.debug(f"Application des permissions sur {dest_path}")
os.chmod(dest_path, 0o644)
logger.debug(f"Téléchargement terminé: {dest_path}")
result[0] = True
@@ -556,78 +517,77 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
return
except requests.exceptions.RequestException as e:
logger.error(f"Tentative {attempt + 1} échouée : {e}")
logger.error(f"Tentative {attempt + 1} échouée: {e}")
if attempt < retries - 1:
logger.debug(f"Attente de {retry_delay} secondes avant nouvelle tentative")
time.sleep(retry_delay)
else:
logger.error("Nombre maximum de tentatives atteint")
logger.error(f"Nombre maximum de tentatives atteint")
result[0] = False
result[1] = _("network_download_failed").format(retries)
return
except requests.exceptions.RequestException as e:
logger.error(f"Erreur API 1fichier : {e}")
logger.error(f"Erreur API 1fichier: {e}")
result[0] = False
result[1] = _("network_api_error").format(str(e))
finally:
logger.debug(f"Thread téléchargement 1fichier terminé pour {url}, task_id={task_id}")
progress_queue.put((task_id, result[0], result[1]))
logger.debug(f"Final result sent to queue: success={result[0]}, message={result[1]}, task_id={task_id}")
progress_queues[task_id].put((task_id, result[0], result[1]))
logger.debug(f"Résultat final envoyé à la queue: success={result[0]}, message={result[1]}, task_id={task_id}")
thread = threading.Thread(target=download_thread)
logger.debug(f"Démarrage thread pour {url}, task_id={task_id}")
thread = threading.Thread(target=download_thread)
thread.start()
# Boucle principale pour mettre à jour la progression
logger.debug(f"Début boucle de progression pour task_id={task_id}")
while thread.is_alive():
try:
while not progress_queue.empty():
data = progress_queue.get()
logger.debug(f"Progress queue data received: {data}")
if len(data) != 3 or data[0] != task_id: # Ignorer les données d'une autre tâche
logger.debug(f"Ignoring queue data for task_id={data[0]}, expected={task_id}")
continue
if isinstance(data[1], bool): # Fin du téléchargement
success, message = data[1], data[2]
# Vérifier si config.history est une liste avant d'itérer
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
# Utiliser une variable intermédiaire pour stocker le message
message_text = message
entry["message"] = message_text
save_history(config.history)
config.needs_redraw = True
logger.debug(f"Final update in history: status={entry['status']}, progress={entry['progress']}%, message={message}, task_id={task_id}")
break
else:
downloaded, total_size = data[1], data[2]
# Calculer le pourcentage correctement et le limiter entre 0 et 100
progress_percent = int(downloaded / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
# Vérifier si config.history est une liste avant d'itérer
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["progress"] = progress_percent
entry["status"] = "Téléchargement"
entry["downloaded_size"] = downloaded
entry["total_size"] = total_size
config.needs_redraw = True
break
await asyncio.sleep(0.2)
task_queue = progress_queues.get(task_id)
if task_queue:
while not task_queue.empty():
data = task_queue.get()
logger.debug(f"Données queue progression reçues: {data}")
if isinstance(data[1], bool): # Fin du téléchargement
success, message = data[1], data[2]
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
save_history(config.history)
config.needs_redraw = True
logger.debug(f"Mise à jour finale historique: status={entry['status']}, progress={entry['progress']}%, message={message}, task_id={task_id}")
break
else:
downloaded, total_size = data[1], data[2]
progress_percent = int(downloaded / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["progress"] = progress_percent
entry["status"] = "Téléchargement"
entry["downloaded_size"] = downloaded
entry["total_size"] = total_size
config.needs_redraw = True
break
await asyncio.sleep(0.1)
except Exception as e:
logger.error(f"Erreur mise à jour progression: {str(e)}")
logger.debug(f"Fin boucle de progression, attente fin thread pour task_id={task_id}")
thread.join()
logger.debug(f"Thread joined for {url}, task_id={task_id}")
logger.debug(f"Thread terminé, nettoyage queue pour task_id={task_id}")
# Nettoyer la queue
if task_id in progress_queues:
del progress_queues[task_id]
logger.debug(f"Fin download_from_1fichier, résultat: success={result[0]}, message={result[1]}")
return result[0], result[1]
def is_1fichier_url(url):
"""Détecte si l'URL est un lien 1fichier."""
return "1fichier.com" in url

View File

@@ -13,6 +13,28 @@ import time
import random
import random
from config import JSON_EXTENSIONS, SAVE_FOLDER
def load_accessibility_settings():
"""Charge les paramètres d'accessibilité depuis accessibility.json."""
accessibility_path = os.path.join(SAVE_FOLDER, "accessibility.json")
try:
if os.path.exists(accessibility_path):
with open(accessibility_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.error(f"Erreur lors du chargement de accessibility.json: {str(e)}")
return {"font_scale": 1.0}
def save_accessibility_settings(settings):
"""Sauvegarde les paramètres d'accessibilité dans accessibility.json."""
accessibility_path = os.path.join(SAVE_FOLDER, "accessibility.json")
try:
os.makedirs(SAVE_FOLDER, exist_ok=True)
with open(accessibility_path, 'w', encoding='utf-8') as f:
json.dump(settings, f, indent=2)
logger.debug(f"Paramètres d'accessibilité sauvegardés: {settings}")
except Exception as e:
logger.error(f"Erreur lors de la sauvegarde de accessibility.json: {str(e)}")
from history import save_history
from language import _ # Import de la fonction de traduction
from datetime import datetime
@@ -85,17 +107,23 @@ def check_extension_before_download(url, platform, game_name):
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 = os.path.join(config.ROMS_FOLDER, platform_dict.get("folder", platform.lower().replace(" ", "")))
dest_dir = os.path.join(config.ROMS_FOLDER, 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(os.path.dirname(os.path.dirname(config.APP_FOLDER)), platform)
for system in extensions_data:
if system["folder"] == dest_dir:
return extension in system["extensions"]
dest_folder_name = os.path.basename(dest_dir)
for i, system in enumerate(extensions_data):
if system["folder"] == dest_folder_name:
result = extension in system["extensions"]
return result
logger.warning(f"Aucun système trouvé pour le dossier {dest_dir}")
return False
@@ -388,7 +416,7 @@ def extract_zip(zip_path, dest_dir, url):
if current_time - last_save_time >= save_interval:
save_history(config.history)
last_save_time = current_time
logger.debug(f"Extraction en cours: {info.filename}, file_extracted={file_extracted}/{file_size}, total_extracted={extracted_size}/{total_size}, progression={progress_percent:.1f}%")
# logger.debug(f"Extraction en cours: {info.filename}, file_extracted={file_extracted}/{file_size}, total_extracted={extracted_size}/{total_size}, progression={progress_percent:.1f}%")
config.needs_redraw = True
break
@@ -574,7 +602,7 @@ def extract_rar(rar_path, dest_dir, url):
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."""
"""Joue une musique aléatoire et configure l'événement de fin de manière non-bloquante."""
if music_files:
# Éviter de rejouer la même musique consécutivement
available_music = [f for f in music_files if f != current_music]
@@ -583,11 +611,21 @@ def play_random_music(music_files, music_folder, current_music=None):
music_file = random.choice(available_music)
music_path = os.path.join(music_folder, music_file)
logger.debug(f"Lecture de la musique : {music_path}")
pygame.mixer.music.load(music_path)
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
set_music_popup(music_file) # Afficher le nom de la musique dans la popup
def load_and_play_music():
try:
pygame.mixer.music.load(music_path)
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
set_music_popup(music_file) # Afficher le nom de la musique dans la popup
except Exception as e:
logger.error(f"Erreur lors du chargement de la musique {music_path}: {str(e)}")
# Charger et jouer la musique dans un thread séparé pour éviter le blocage
music_thread = threading.Thread(target=load_and_play_music, daemon=True)
music_thread.start()
return music_file # Retourner la nouvelle musique pour mise à jour
else:
logger.debug("Aucune musique trouvée dans /RGSX/assets/music")
@@ -602,13 +640,17 @@ def set_music_popup(music_name):
def load_api_key_1fichier():
"""Charge la clé API 1fichier depuis le dossier de sauvegarde, crée le fichier si absent."""
api_path = os.path.join(SAVE_FOLDER, "1fichierAPI.txt")
logger.debug(f"Tentative de chargement de la clé API depuis: {api_path}")
try:
# Vérifie si le fichier existe déjà
if not os.path.exists(api_path):
# Crée le dossier parent si nécessaire
os.makedirs(SAVE_FOLDER, exist_ok=True)
# Crée le fichier vide si absent
with open(api_path, "w") as f:
f.write("")
logger.info(f"Fichier de clé API créé : {api_path}")
return ""
except OSError as e:
logger.error(f"Erreur lors de la création du fichier de clé API : {e}")
return ""
@@ -616,9 +658,10 @@ def load_api_key_1fichier():
try:
with open(api_path, "r", encoding="utf-8") as f:
api_key = f.read().strip()
logger.debug(f"Clé API 1fichier chargée : {api_key}")
logger.debug(f"Clé API 1fichier lue: '{api_key}' (longueur: {len(api_key)})")
if not api_key:
logger.warning("Clé API 1fichier vide, veuillez la renseigner dans le fichier pour pouvoir utiliser les fonctionnalités de téléchargement sur 1fichier.")
config.API_KEY_1FICHIER = api_key
return api_key
except OSError as e:
logger.error(f"Erreur lors de la lecture de la clé API : {e}")