Amélioration historique, suppression fenetre progression intégrée, et ajout controles en bas de l'ecran

This commit is contained in:
skymike03
2025-07-15 12:28:32 +02:00
parent 78343143ad
commit 6958897a45
8 changed files with 558 additions and 280 deletions

View File

@@ -5,12 +5,14 @@ import asyncio
import platform import platform
import logging import logging
import requests 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, 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_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 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 import handle_controls, validate_menu_state
from controls_mapper import load_controls_config, map_controls, draw_controls_mapping, ACTIONS from controls_mapper import load_controls_config, map_controls, draw_controls_mapping, ACTIONS
from utils import detect_non_pc, load_sources, check_extension_before_download, extract_zip, play_random_music from utils import detect_non_pc, load_sources, check_extension_before_download, extract_zip, play_random_music
from history import load_history from history import load_history, save_history
import config import config
from config import OTA_VERSION_ENDPOINT, OTA_UPDATE_SCRIPT, OTA_data_ZIP from config import OTA_VERSION_ENDPOINT, OTA_UPDATE_SCRIPT, OTA_data_ZIP
@@ -132,7 +134,7 @@ async def main():
clock = pygame.time.Clock() clock = pygame.time.Clock()
while running: while running:
clock.tick(60) # Limite à 60 FPS clock.tick(30) # Limite à 60 FPS
if config.update_triggered: if config.update_triggered:
logger.debug("Mise à jour déclenchée, arrêt de la boucle principale") logger.debug("Mise à jour déclenchée, arrêt de la boucle principale")
break break
@@ -143,6 +145,12 @@ async def main():
if config.menu_state == "download_progress" and current_time - last_redraw_time >= 100: if config.menu_state == "download_progress" and current_time - last_redraw_time >= 100:
config.needs_redraw = True config.needs_redraw = True
last_redraw_time = current_time last_redraw_time = current_time
# Forcer redraw toutes les 100 ms dans history avec téléchargement actif
if config.menu_state == "history" and any(entry["status"] == "Téléchargement" for entry in config.history):
if current_time - last_redraw_time >= 100:
config.needs_redraw = True
last_redraw_time = current_time
# logger.debug("Forcing redraw in history state due to active download")
# Gestion de la fin du popup # Gestion de la fin du popup
if config.menu_state == "restart_popup" and config.popup_timer > 0: if config.menu_state == "restart_popup" and config.popup_timer > 0:
@@ -218,7 +226,40 @@ async def main():
logger.debug(f"Événement transmis à handle_controls dans redownload_game_cache: {event.type}") logger.debug(f"Événement transmis à handle_controls dans redownload_game_cache: {event.type}")
continue continue
if config.menu_state in ["platform", "game", "error", "confirm_exit", "download_progress", "download_result", "extension_warning", "history"]: if config.menu_state == "extension_warning":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
if action == "confirm":
if config.pending_download and config.extension_confirm_selection == 0: # Oui
url, platform, game_name, is_zip_non_supported = config.pending_download
logger.debug(f"Téléchargement confirmé après avertissement: {game_name} pour {platform} depuis {url}")
task_id = str(pygame.time.get_ticks())
config.history.append({
"platform": platform,
"game_name": game_name,
"status": "downloading",
"progress": 0,
"url": url,
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
config.current_history_item = len(config.history) - 1
save_history(config.history)
config.download_tasks[task_id] = (
asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported, task_id)),
url, game_name, platform
)
config.menu_state = "history"
config.pending_download = None
config.needs_redraw = True
logger.debug(f"Téléchargement démarré pour {game_name}, task_id={task_id}")
elif config.extension_confirm_selection == 1: # Non
config.menu_state = config.previous_menu_state
config.pending_download = None
config.needs_redraw = True
logger.debug("Téléchargement annulé, retour à l'état précédent")
continue
if config.menu_state in ["platform", "game", "error", "confirm_exit", "download_progress", "download_result", "history"]:
action = handle_controls(event, sources, joystick, screen) action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True config.needs_redraw = True
if action == "quit": if action == "quit":
@@ -227,10 +268,20 @@ async def main():
elif action == "download" and config.menu_state == "game" and config.filtered_games: elif action == "download" and config.menu_state == "game" and config.filtered_games:
game = config.filtered_games[config.current_game] game = config.filtered_games[config.current_game]
game_name = game[0] if isinstance(game, (list, tuple)) else game game_name = game[0] if isinstance(game, (list, tuple)) else game
platform = config.platforms[config.current_platform] platform = config.platforms[config.current_platform]["name"] # Utiliser le nom de la plateforme
url = game[1] if isinstance(game, (list, tuple)) and len(game) > 1 else None url = game[1] if isinstance(game, (list, tuple)) and len(game) > 1 else None
if url: if url:
logger.debug(f"Vérification pour {game_name}, URL: {url}") logger.debug(f"Vérification pour {game_name}, URL: {url}")
# Ajouter une entrée temporaire à l'historique
config.history.append({
"platform": platform,
"game_name": game_name,
"status": "downloading",
"progress": 0,
"url": url,
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
config.current_history_item = len(config.history) - 1 # Sélectionner l'entrée en cours
if is_1fichier_url(url): if is_1fichier_url(url):
if not config.API_KEY_1FICHIER: if not config.API_KEY_1FICHIER:
config.previous_menu_state = config.menu_state config.previous_menu_state = config.menu_state
@@ -238,6 +289,11 @@ async def main():
config.error_message = ( config.error_message = (
"Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt" "Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt"
) )
# Mettre à jour l'entrée temporaire avec l'erreur
config.history[-1]["status"] = "Erreur"
config.history[-1]["progress"] = 0
config.history[-1]["message"] = "Erreur API : Clé API 1fichier absente"
save_history(config.history)
config.needs_redraw = True config.needs_redraw = True
logger.error("Clé API 1fichier absente") logger.error("Clé API 1fichier absente")
config.pending_download = None config.pending_download = None
@@ -249,18 +305,20 @@ async def main():
config.extension_confirm_selection = 0 config.extension_confirm_selection = 0
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Extension non reconnue pour lien 1fichier, passage à extension_warning pour {game_name}") logger.debug(f"Extension non reconnue pour lien 1fichier, passage à extension_warning pour {game_name}")
# Supprimer l'entrée temporaire si erreur
config.history.pop()
else: else:
config.previous_menu_state = config.menu_state config.previous_menu_state = config.menu_state
logger.debug(f"Previous menu state défini: {config.previous_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) # Lancer le téléchargement dans une tâche asynchrone
config.download_result_message = message task_id = str(pygame.time.get_ticks())
config.download_result_error = not success config.download_tasks[task_id] = (
config.download_result_start_time = pygame.time.get_ticks() asyncio.create_task(download_from_1fichier(url, platform, game_name, is_zip_non_supported)),
config.menu_state = "download_result" url, game_name, platform
config.download_progress.clear() )
config.pending_download = None config.menu_state = "history" # Passer à l'historique
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Téléchargement 1fichier terminé pour {game_name}, succès={success}, message={message}") logger.debug(f"Téléchargement 1fichier démarré pour {game_name}, passage à l'historique")
else: else:
is_supported, message, is_zip_non_supported = check_extension_before_download(url, platform, game_name) is_supported, message, is_zip_non_supported = check_extension_before_download(url, platform, game_name)
if not is_supported: if not is_supported:
@@ -269,18 +327,20 @@ async def main():
config.extension_confirm_selection = 0 config.extension_confirm_selection = 0
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Extension non reconnue, passage à extension_warning pour {game_name}") logger.debug(f"Extension non reconnue, passage à extension_warning pour {game_name}")
# Supprimer l'entrée temporaire si erreur
config.history.pop()
else: else:
config.previous_menu_state = config.menu_state config.previous_menu_state = config.menu_state
logger.debug(f"Previous menu state défini: {config.previous_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) # Lancer le téléchargement dans une tâche asynchrone
config.download_result_message = message task_id = str(pygame.time.get_ticks())
config.download_result_error = not success config.download_tasks[task_id] = (
config.download_result_start_time = pygame.time.get_ticks() asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported)),
config.menu_state = "download_result" url, game_name, platform
config.download_progress.clear() )
config.pending_download = None config.menu_state = "history" # Passer à l'historique
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Téléchargement terminé pour {game_name}, succès={success}, message={message}") logger.debug(f"Téléchargement démarré pour {game_name}, passage à l'historique")
elif action == "redownload" and config.menu_state == "history" and config.history: elif action == "redownload" and config.menu_state == "history" and config.history:
entry = config.history[config.current_history_item] entry = config.history[config.current_history_item]
platform = entry["platform"] platform = entry["platform"]
@@ -340,13 +400,27 @@ async def main():
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Retéléchargement terminé pour {game_name}, succès={success}, message={message}") logger.debug(f"Retéléchargement terminé pour {game_name}, succès={success}, message={message}")
break break
# Gestion des téléchargements # Gestion des téléchargements
if config.download_tasks: if config.download_tasks:
for task_id, (task, url, game_name, platform) in list(config.download_tasks.items()): for task_id, (task, url, game_name, platform) in list(config.download_tasks.items()):
if task.done(): if task.done():
try: try:
success, message = await task success, message = await task
if "http" in message:
message = message.split("https://")[0].strip()
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
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"Téléchargement terminé: {game_name}, succès={success}, message={message}, task_id={task_id}")
break
config.download_result_message = message config.download_result_message = message
config.download_result_error = not success config.download_result_error = not success
config.download_result_start_time = pygame.time.get_ticks() config.download_result_start_time = pygame.time.get_ticks()
@@ -355,9 +429,59 @@ async def main():
config.pending_download = None config.pending_download = None
config.needs_redraw = True config.needs_redraw = True
del config.download_tasks[task_id] del config.download_tasks[task_id]
logger.debug(f"Téléchargement terminé: {game_name}, succès={success}, message={message}")
except Exception as e: except Exception as e:
config.download_result_message = f"Erreur lors du téléchargement : {str(e)}" message = f"Erreur lors du téléchargement: {str(e)}"
if "http" in message:
message = message.split("https://")[0].strip()
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["status"] = "Erreur"
entry["progress"] = 0
entry["message"] = message
save_history(config.history)
config.needs_redraw = True
logger.debug(f"Erreur téléchargement: {game_name}, message={message}, task_id={task_id}")
break
config.download_result_message = message
config.download_result_error = True
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
del config.download_tasks[task_id]
else:
# Traiter les mises à jour de progression
progress_queue = queue.Queue()
while not progress_queue.empty():
data = progress_queue.get()
# logger.debug(f"Progress queue data received: {data}, task_id={task_id}")
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]
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
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 = (downloaded / total_size * 100) if total_size > 0 else 0
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["progress"] = progress
entry["status"] = "Téléchargement"
config.needs_redraw = True
# logger.debug(f"Progress updated in history: {progress:.1f}% for {game_name}, task_id={task_id}")
break
config.download_result_message = message
config.download_result_error = True config.download_result_error = True
config.download_result_start_time = pygame.time.get_ticks() config.download_result_start_time = pygame.time.get_ticks()
config.menu_state = "download_result" config.menu_state = "download_result"
@@ -365,19 +489,20 @@ async def main():
config.pending_download = None config.pending_download = None
config.needs_redraw = True config.needs_redraw = True
del config.download_tasks[task_id] del config.download_tasks[task_id]
logger.error(f"Erreur dans tâche de téléchargement: {str(e)}")
# Gestion de la fin du popup download_result # Gestion de la fin du popup download_result
if config.menu_state == "download_result" and current_time - config.download_result_start_time > 3000: if config.menu_state == "download_result" and current_time - config.download_result_start_time > 3000:
config.menu_state = config.previous_menu_state if config.previous_menu_state in ["platform", "game", "history"] else "game" config.menu_state = "history" # Rester dans l'historique après le popup
config.download_progress.clear() config.download_progress.clear()
config.pending_download = None config.pending_download = None
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Fin popup download_result, retour à {config.menu_state}") logger.debug(f"Fin popup download_result, retour à history")
# Affichage # Affichage
if config.needs_redraw: if config.needs_redraw:
draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"]) draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"])
if config.menu_state == "controls_mapping": if config.menu_state == "controls_mapping":
draw_controls_mapping(screen, ACTIONS[0], None, False, 0.0) draw_controls_mapping(screen, ACTIONS[0], None, False, 0.0)
elif config.menu_state == "loading": elif config.menu_state == "loading":
@@ -406,7 +531,8 @@ async def main():
elif config.menu_state == "controls_help": elif config.menu_state == "controls_help":
draw_controls_help(screen, config.previous_menu_state) draw_controls_help(screen, config.previous_menu_state)
elif config.menu_state == "history": elif config.menu_state == "history":
draw_history_list(screen) draw_history_list(screen)
# logger.debug("Screen updated with draw_history_list")
elif config.menu_state == "confirm_clear_history": elif config.menu_state == "confirm_clear_history":
draw_clear_history_dialog(screen) draw_clear_history_dialog(screen)
elif config.menu_state == "redownload_game_cache": elif config.menu_state == "redownload_game_cache":
@@ -420,7 +546,9 @@ async def main():
logger.error(f"État de menu non valide détecté: {config.menu_state}, retour à platform") logger.error(f"État de menu non valide détecté: {config.menu_state}, retour à platform")
draw_controls(screen, config.menu_state) draw_controls(screen, config.menu_state)
pygame.display.flip() pygame.display.flip()
config.needs_redraw = False config.needs_redraw = False
# logger.debug("Screen flipped with pygame.display.flip()")
# Gestion de l'état controls_mapping # Gestion de l'état controls_mapping
if config.menu_state == "controls_mapping": if config.menu_state == "controls_mapping":

Binary file not shown.

View File

@@ -5,7 +5,7 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Version actuelle de l'application # Version actuelle de l'application
app_version = "1.9.5" app_version = "1.9.6"
# URL du serveur OTA # URL du serveur OTA
@@ -37,11 +37,7 @@ transition_state = "idle"
transition_progress = 0.0 transition_progress = 0.0
transition_duration = 18 transition_duration = 18
games_count = {} games_count = {}
download_tasks = {}
download_progress = {}
download_result_message = ""
download_result_error = False
download_result_start_time = 0
loading_progress = 0.0 loading_progress = 0.0
current_loading_system = "" current_loading_system = ""
error_message = "" error_message = ""
@@ -58,8 +54,16 @@ pending_download = None
controls_config = {} controls_config = {}
selected_option = 0 selected_option = 0
previous_menu_state = None previous_menu_state = None
history = [] # Liste des entrées de l'historique history = [] # Liste des entrées d'historique avec platform, game_name, status, url, progress, message, timestamp
current_history_item = 0 # Index de l'élément sélectionné dans l'historique download_progress = {}
download_tasks = {} # Dictionnaire pour les tâches de téléchargement
download_result_message = ""
download_result_error = False
download_result_start_time = 0
pending_download = None
needs_redraw = False
current_history_item = 0
history_scroll_offset = 0
history_scroll_offset = 0 # Offset pour le défilement de l'historique 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) visible_history_items = 15 # Nombre d'éléments d'historique visibles (ajusté dynamiquement)
confirm_clear_selection = 0 # confirmation clear historique confirm_clear_selection = 0 # confirmation clear historique

View File

@@ -8,7 +8,7 @@ import os
from display import draw_validation_transition from display import draw_validation_transition
from network import download_rom, download_from_1fichier, is_1fichier_url 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
from history import load_history, clear_history from history import load_history, clear_history, add_to_history, save_history
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -177,7 +177,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_last_action = current_time 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.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 config.needs_redraw = True
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}") # logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
elif is_input_matched(event, "up"): elif is_input_matched(event, "up"):
if current_grid_index - GRID_COLS >= 0: if current_grid_index - GRID_COLS >= 0:
config.selected_platform -= GRID_COLS config.selected_platform -= GRID_COLS
@@ -186,7 +186,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_last_action = current_time 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.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 config.needs_redraw = True
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}") # logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
elif is_input_matched(event, "left"): elif is_input_matched(event, "left"):
if col > 0: if col > 0:
config.selected_platform -= 1 config.selected_platform -= 1
@@ -195,7 +195,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_last_action = current_time 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.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 config.needs_redraw = True
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}") # logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
elif config.current_page > 0: elif config.current_page > 0:
config.current_page -= 1 config.current_page -= 1
config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS + (GRID_COLS - 1) config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS + (GRID_COLS - 1)
@@ -206,7 +206,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_last_action = current_time 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.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 config.needs_redraw = True
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}") # logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
elif is_input_matched(event, "right"): elif is_input_matched(event, "right"):
if col < GRID_COLS - 1 and current_grid_index < max_index: if col < GRID_COLS - 1 and current_grid_index < max_index:
config.selected_platform += 1 config.selected_platform += 1
@@ -215,7 +215,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_last_action = current_time 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.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 config.needs_redraw = True
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}") # logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
elif (config.current_page + 1) * systems_per_page < len(config.platforms): elif (config.current_page + 1) * systems_per_page < len(config.platforms):
config.current_page += 1 config.current_page += 1
config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS
@@ -226,7 +226,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_last_action = current_time 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.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 config.needs_redraw = True
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}") # logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
elif is_input_matched(event, "page_down"): elif is_input_matched(event, "page_down"):
if (config.current_page + 1) * systems_per_page < len(config.platforms): if (config.current_page + 1) * systems_per_page < len(config.platforms):
config.current_page += 1 config.current_page += 1
@@ -238,7 +238,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0 config.repeat_start_time = 0
config.repeat_last_action = current_time config.repeat_last_action = current_time
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}") # logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
#logger.debug("Page suivante, répétition réinitialisée") #logger.debug("Page suivante, répétition réinitialisée")
elif is_input_matched(event, "page_up"): elif is_input_matched(event, "page_up"):
if config.current_page > 0: if config.current_page > 0:
@@ -251,7 +251,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0 config.repeat_start_time = 0
config.repeat_last_action = current_time config.repeat_last_action = current_time
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}") # logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
#logger.debug("Page précédente, répétition réinitialisée") #logger.debug("Page précédente, répétition réinitialisée")
elif is_input_matched(event, "page_up"): elif is_input_matched(event, "page_up"):
if config.current_page > 0: if config.current_page > 0:
@@ -264,7 +264,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0 config.repeat_start_time = 0
config.repeat_last_action = current_time config.repeat_last_action = current_time
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Plateforme sélectionnée: {config.selected_platform}") # logger.debug(f"Plateforme sélectionnée: {config.selected_platform}")
#logger.debug("Page précédente, répétition réinitialisée") #logger.debug("Page précédente, répétition réinitialisée")
elif is_input_matched(event, "progress"): elif is_input_matched(event, "progress"):
if config.download_tasks: if config.download_tasks:
@@ -411,7 +411,6 @@ def handle_controls(event, sources, joystick, screen):
logger.debug("Sortie du mode recherche") logger.debug("Sortie du mode recherche")
else: else:
if is_input_matched(event, "up"): if is_input_matched(event, "up"):
if config.current_game > 0: if config.current_game > 0:
config.current_game -= 1 config.current_game -= 1
@@ -435,7 +434,6 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0 config.repeat_start_time = 0
config.repeat_last_action = current_time config.repeat_last_action = current_time
config.needs_redraw = True config.needs_redraw = True
#logger.debug("Page précédente dans la liste des jeux")
elif is_input_matched(event, "page_down"): elif is_input_matched(event, "page_down"):
config.current_game = min(len(games) - 1, config.current_game + config.visible_games) config.current_game = min(len(games) - 1, config.current_game + config.visible_games)
config.repeat_action = None config.repeat_action = None
@@ -443,7 +441,6 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0 config.repeat_start_time = 0
config.repeat_last_action = current_time config.repeat_last_action = current_time
config.needs_redraw = True config.needs_redraw = True
#logger.debug("Page suivante dans la liste des jeux")
elif is_input_matched(event, "filter"): elif is_input_matched(event, "filter"):
config.search_mode = True config.search_mode = True
config.search_query = "" config.search_query = ""
@@ -462,8 +459,7 @@ def handle_controls(event, sources, joystick, screen):
elif is_input_matched(event, "history"): elif is_input_matched(event, "history"):
config.menu_state = "history" config.menu_state = "history"
config.needs_redraw = True config.needs_redraw = True
logger.debug("Ouverture history depuis game") logger.debug("Ouverture history depuis game")
elif is_input_matched(event, "cancel"): elif is_input_matched(event, "cancel"):
config.menu_state = "platform" config.menu_state = "platform"
config.current_game = 0 config.current_game = 0
@@ -475,27 +471,39 @@ def handle_controls(event, sources, joystick, screen):
config.menu_state = "redownload_game_cache" config.menu_state = "redownload_game_cache"
config.needs_redraw = True config.needs_redraw = True
logger.debug("Passage à redownload_game_cache depuis game") logger.debug("Passage à redownload_game_cache depuis game")
# Sélectionner un jeu, événement confirm
# Sélectionner un jeu , evenent confirm
elif is_input_matched(event, "confirm"): elif is_input_matched(event, "confirm"):
if games: if games:
url = games[config.current_game][1] url = games[config.current_game][1]
game_name = games[config.current_game][0] game_name = games[config.current_game][0]
platform = config.platforms[config.current_platform] platform = config.platforms[config.current_platform]["name"] if isinstance(config.platforms[config.current_platform], dict) else config.platforms[config.current_platform]
logger.debug(f"Vérification pour {game_name}, URL: {url}") logger.debug(f"Vérification pour {game_name}, URL: {url}")
# Ajouter une entrée temporaire à l'historique
config.history.append(add_to_history(
platform=platform,
game_name=game_name,
status="downloading",
url=url,
progress=0,
message="Téléchargement en cours"
))
config.current_history_item = len(config.history) - 1
# Vérifier d'abord si c'est un lien 1fichier # Vérifier d'abord si c'est un lien 1fichier
if is_1fichier_url(url): if is_1fichier_url(url):
if not config.API_KEY_1FICHIER: if not config.API_KEY_1FICHIER:
config.previous_menu_state = config.menu_state config.previous_menu_state = config.menu_state
config.menu_state = "error" config.menu_state = "error"
config.error_message = ( 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" "Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt à ouvrir dans un éditeur de texte et coller la clé API"
) )
config.history[-1]["status"] = "Erreur"
config.history[-1]["progress"] = 0
config.history[-1]["message"] = "Erreur API : Clé API 1fichier absente"
save_history(config.history)
config.needs_redraw = True config.needs_redraw = True
logger.error("Clé API 1fichier absente, téléchargement impossible.") logger.error("Clé API 1fichier absente, téléchargement impossible.")
config.pending_download = None config.pending_download = None
return action return action
# Vérifier l'extension pour les liens 1fichier
config.pending_download = check_extension_before_download(url, platform, game_name) config.pending_download = check_extension_before_download(url, platform, game_name)
if config.pending_download: if config.pending_download:
is_supported = is_extension_supported( is_supported = is_extension_supported(
@@ -509,14 +517,15 @@ def handle_controls(event, sources, joystick, screen):
config.extension_confirm_selection = 0 config.extension_confirm_selection = 0
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Extension non supportée, passage à extension_warning pour {game_name}") logger.debug(f"Extension non supportée, passage à extension_warning pour {game_name}")
config.history.pop() # Supprimer l'entrée temporaire
else: else:
loop = asyncio.get_running_loop() task_id = str(pygame.time.get_ticks())
task = loop.run_in_executor(None, download_from_1fichier, url, platform, game_name, config.pending_download[3]) task = asyncio.create_task(download_from_1fichier(url, platform, game_name, config.pending_download[3], task_id))
config.download_tasks[task] = (task, url, game_name, platform) config.download_tasks[task_id] = (task, url, game_name, platform)
config.previous_menu_state = config.menu_state config.previous_menu_state = config.menu_state
config.menu_state = "download_progress" config.menu_state = "history" # Passer à l'historique
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Début du téléchargement 1fichier: {game_name} pour {platform} depuis {url}") logger.debug(f"Début du téléchargement 1fichier: {game_name} pour {platform} depuis {url}, task_id={task_id}")
config.pending_download = None config.pending_download = None
action = "download" action = "download"
else: else:
@@ -525,8 +534,8 @@ def handle_controls(event, sources, joystick, screen):
config.pending_download = None config.pending_download = None
config.needs_redraw = True config.needs_redraw = True
logger.error(f"config.pending_download est None pour {game_name}") logger.error(f"config.pending_download est None pour {game_name}")
config.history.pop() # Supprimer l'entrée temporaire
else: else:
# Vérifier l'extension pour les liens non-1fichier
config.pending_download = check_extension_before_download(url, platform, game_name) config.pending_download = check_extension_before_download(url, platform, game_name)
if config.pending_download: if config.pending_download:
is_supported = is_extension_supported( is_supported = is_extension_supported(
@@ -540,13 +549,15 @@ def handle_controls(event, sources, joystick, screen):
config.extension_confirm_selection = 0 config.extension_confirm_selection = 0
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Extension non supportée, passage à extension_warning pour {game_name}") logger.debug(f"Extension non supportée, passage à extension_warning pour {game_name}")
config.history.pop() # Supprimer l'entrée temporaire
else: else:
task = asyncio.create_task(download_rom(url, platform, game_name, config.pending_download[3])) task_id = str(pygame.time.get_ticks())
config.download_tasks[task] = (task, url, game_name, platform) task = asyncio.create_task(download_rom(url, platform, game_name, config.pending_download[3], task_id))
config.download_tasks[task_id] = (task, url, game_name, platform)
config.previous_menu_state = config.menu_state config.previous_menu_state = config.menu_state
config.menu_state = "download_progress" config.menu_state = "history" # Passer à l'historique
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Début du téléchargement: {game_name} pour {platform} depuis {url}") logger.debug(f"Début du téléchargement: {game_name} pour {platform} depuis {url}, task_id={task_id}")
config.pending_download = None config.pending_download = None
action = "download" action = "download"
else: else:
@@ -555,6 +566,7 @@ def handle_controls(event, sources, joystick, screen):
config.pending_download = None config.pending_download = None
config.needs_redraw = True config.needs_redraw = True
logger.error(f"config.pending_download est None pour {game_name}") logger.error(f"config.pending_download est None pour {game_name}")
config.history.pop() # Supprimer l'entrée temporaire
# Avertissement extension # Avertissement extension
elif config.menu_state == "extension_warning": elif config.menu_state == "extension_warning":
@@ -562,6 +574,16 @@ def handle_controls(event, sources, joystick, screen):
if config.extension_confirm_selection == 1: if config.extension_confirm_selection == 1:
if config.pending_download and len(config.pending_download) == 4: if config.pending_download and len(config.pending_download) == 4:
url, platform, game_name, is_zip_non_supported = config.pending_download url, platform, game_name, is_zip_non_supported = config.pending_download
# Ajouter une entrée temporaire à l'historique
config.history.append(add_to_history(
platform=platform,
game_name=game_name,
status="downloading",
url=url,
progress=0,
message="Téléchargement en cours"
))
config.current_history_item = len(config.history) - 1
if is_1fichier_url(url): if is_1fichier_url(url):
if not config.API_KEY_1FICHIER: if not config.API_KEY_1FICHIER:
config.previous_menu_state = config.menu_state config.previous_menu_state = config.menu_state
@@ -569,19 +591,24 @@ def handle_controls(event, sources, joystick, screen):
config.error_message = ( config.error_message = (
"Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt" "Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt"
) )
config.history[-1]["status"] = "Erreur"
config.history[-1]["progress"] = 0
config.history[-1]["message"] = "Erreur API : Clé API 1fichier absente"
save_history(config.history)
config.needs_redraw = True config.needs_redraw = True
logger.error("Clé API 1fichier absente, téléchargement impossible.") logger.error("Clé API 1fichier absente, téléchargement impossible.")
config.pending_download = None config.pending_download = None
return action return action
loop = asyncio.get_running_loop() task_id = str(pygame.time.get_ticks())
task = loop.run_in_executor(None, download_from_1fichier, url, platform, game_name, is_zip_non_supported) task = asyncio.create_task(download_from_1fichier(url, platform, game_name, is_zip_non_supported, task_id))
else: else:
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported)) task_id = str(pygame.time.get_ticks())
config.download_tasks[task] = (task, url, game_name, platform) task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported, task_id))
config.download_tasks[task_id] = (task, url, game_name, platform)
config.previous_menu_state = validate_menu_state(config.previous_menu_state) config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "download_progress" config.menu_state = "history" # Passer à l'historique
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Téléchargement confirmé après avertissement: {game_name} pour {platform} depuis {url}") logger.debug(f"Téléchargement confirmé après avertissement: {game_name} pour {platform} depuis {url}, task_id={task_id}")
config.pending_download = None config.pending_download = None
action = "download" action = "download"
else: else:
@@ -590,6 +617,7 @@ def handle_controls(event, sources, joystick, screen):
config.pending_download = None config.pending_download = None
config.needs_redraw = True config.needs_redraw = True
logger.error("config.pending_download invalide") logger.error("config.pending_download invalide")
config.history.pop() # Supprimer l'entrée temporaire
else: else:
config.pending_download = None config.pending_download = None
config.menu_state = validate_menu_state(config.previous_menu_state) config.menu_state = validate_menu_state(config.previous_menu_state)
@@ -598,7 +626,6 @@ def handle_controls(event, sources, joystick, screen):
elif is_input_matched(event, "left") or is_input_matched(event, "right"): elif is_input_matched(event, "left") or is_input_matched(event, "right"):
config.extension_confirm_selection = 1 - config.extension_confirm_selection config.extension_confirm_selection = 1 - config.extension_confirm_selection
config.needs_redraw = True config.needs_redraw = True
#logger.debug(f"Changement sélection extension_warning: {config.extension_confirm_selection}")
elif is_input_matched(event, "cancel"): elif is_input_matched(event, "cancel"):
config.pending_download = None config.pending_download = None
config.menu_state = validate_menu_state(config.previous_menu_state) config.menu_state = validate_menu_state(config.previous_menu_state)
@@ -653,22 +680,40 @@ def handle_controls(event, sources, joystick, screen):
game_name = entry["game_name"] game_name = entry["game_name"]
for game in config.games: for game in config.games:
if game[0] == game_name and config.platforms[config.current_platform] == platform: if game[0] == game_name and config.platforms[config.current_platform] == platform:
config.pending_download = check_extension_before_download(game_name, platform, game[1]) config.pending_download = check_extension_before_download(game[1], platform, game_name)
if config.pending_download: if config.pending_download:
url, platform, game_name, is_zip_non_supported = config.pending_download url, platform, game_name, is_zip_non_supported = config.pending_download
if is_zip_non_supported: if is_zip_non_supported:
config.previous_menu_state = config.menu_state # Remplacer cette ligne config.previous_menu_state = config.menu_state
config.menu_state = "extension_warning" config.menu_state = "extension_warning"
config.extension_confirm_selection = 0 config.extension_confirm_selection = 0
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Extension non supportée pour retéléchargement, passage à extension_warning pour {game_name}") logger.debug(f"Extension non supportée pour retéléchargement, passage à extension_warning pour {game_name}")
else: else:
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported)) task_id = str(pygame.time.get_ticks())
config.download_tasks[task] = (task, url, game_name, platform) if is_1fichier_url(url):
config.previous_menu_state = config.menu_state # Remplacer cette ligne if not config.API_KEY_1FICHIER:
config.menu_state = "download_progress" 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.history[-1]["status"] = "Erreur"
config.history[-1]["progress"] = 0
config.history[-1]["message"] = "Erreur API : Clé API 1fichier absente"
save_history(config.history)
config.needs_redraw = True
logger.error("Clé API 1fichier absente, retéléchargement impossible.")
config.pending_download = None
return action
task = asyncio.create_task(download_from_1fichier(url, platform, game_name, is_zip_non_supported, task_id))
else:
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported, task_id))
config.download_tasks[task_id] = (task, url, game_name, platform)
config.previous_menu_state = config.menu_state
config.menu_state = "history"
config.needs_redraw = True config.needs_redraw = True
logger.debug(f"Retéléchargement: {game_name} pour {platform} depuis {url}") logger.debug(f"Retéléchargement: {game_name} pour {platform} depuis {url}, task_id={task_id}")
config.pending_download = None config.pending_download = None
action = "redownload" action = "redownload"
else: else:

View File

@@ -1,6 +1,6 @@
import pygame # type: ignore import pygame # type: ignore
import config import config
from utils import truncate_text_middle, wrap_text, load_system_image from utils import truncate_text_middle, wrap_text, load_system_image, truncate_text_end
import logging import logging
import math import math
from history import load_history # Ajout de l'import from history import load_history # Ajout de l'import
@@ -437,13 +437,20 @@ def draw_game_scrollbar(screen, scroll_offset, total_items, visible_items, x, y,
pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], (x, scrollbar_y, 15, scrollbar_height), border_radius=4) pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], (x, scrollbar_y, 15, scrollbar_height), border_radius=4)
def draw_history_list(screen): def draw_history_list(screen):
"""Affiche l'historique des téléchargements avec un style moderne.""" # logger.debug(f"Dessin historique, history={config.history}, needs_redraw={config.needs_redraw}")
history = config.history if hasattr(config, 'history') else load_history() history = config.history if hasattr(config, 'history') else load_history()
history_count = len(history) history_count = len(history)
col_platform_width = int((0.95 * config.screen_width - 60) * 0.33) # Define column widths as percentages of available space
col_game_width = int((0.95 * config.screen_width - 60) * 0.50) column_width_percentages = {
col_status_width = int((0.95 * config.screen_width - 60) * 0.17) "platform": 0.25, # platform column
"game_name": 0.50, # game name column
"status": 0.25 # status column
}
available_width = int(0.95 * config.screen_width - 60) # Total available width for columns
col_platform_width = int(available_width * column_width_percentages["platform"])
col_game_width = int(available_width * column_width_percentages["game_name"])
col_status_width = int(available_width * column_width_percentages["status"])
rect_width = int(0.95 * config.screen_width) rect_width = int(0.95 * config.screen_width)
line_height = config.small_font.get_height() + 10 line_height = config.small_font.get_height() + 10
@@ -514,7 +521,6 @@ def draw_history_list(screen):
text_rect = text_surface.get_rect(center=(x_pos, header_y)) text_rect = text_surface.get_rect(center=(x_pos, header_y))
screen.blit(text_surface, text_rect) screen.blit(text_surface, text_rect)
# Ajouter un séparateur sous les en-têtes
separator_y = rect_y + margin_top_bottom + header_height separator_y = rect_y + margin_top_bottom + header_height
pygame.draw.line(screen, THEME_COLORS["border"], (rect_x + 20, separator_y), (rect_x + rect_width - 20, separator_y), 2) pygame.draw.line(screen, THEME_COLORS["border"], (rect_x + 20, separator_y), (rect_x + rect_width - 20, separator_y), 2)
@@ -523,10 +529,28 @@ def draw_history_list(screen):
platform = entry.get("platform", "Inconnu") platform = entry.get("platform", "Inconnu")
game_name = entry.get("game_name", "Inconnu") game_name = entry.get("game_name", "Inconnu")
status = entry.get("status", "Inconnu") status = entry.get("status", "Inconnu")
progress = entry.get("progress", 0)
# Personnaliser l'affichage du statut
if status in ["Téléchargement", "downloading"]:
status_text = f"Téléchargement : {progress:.1f}%"
# logger.debug(f"Affichage progression: {progress:.1f}% pour {game_name}, status={status_text}")
elif status == "Extracting":
status_text = f"Extraction : {progress:.1f}%"
# logger.debug(f"Affichage extraction: {progress:.1f}% pour {game_name}, status={status_text}")
elif status == "Download_OK":
status_text = "Terminé"
# logger.debug(f"Affichage terminé: {game_name}, status={status_text}")
elif status == "Erreur":
status_text = f"Erreur : {entry.get('message', 'Échec')}"
logger.debug(f"Affichage erreur: {game_name}, status={status_text}")
else:
status_text = status
logger.debug(f"Affichage statut inconnu: {game_name}, status={status_text}")
color = THEME_COLORS["fond_lignes"] if i == config.current_history_item else THEME_COLORS["text"] color = THEME_COLORS["fond_lignes"] if i == config.current_history_item else THEME_COLORS["text"]
platform_text = truncate_text_middle(platform, config.small_font, col_platform_width - 10) platform_text = truncate_text_end(platform, config.small_font, col_platform_width - 10)
game_text = truncate_text_middle(game_name, config.small_font, col_game_width - 10) game_text = truncate_text_end(game_name, config.small_font, col_game_width - 10)
status_text = truncate_text_middle(status, config.small_font, col_status_width - 10) status_text = truncate_text_middle(status_text, config.small_font, col_status_width - 10, is_filename=False)
y_pos = rect_y + margin_top_bottom + header_height + idx * line_height + line_height // 2 y_pos = rect_y + margin_top_bottom + header_height + idx * line_height + line_height // 2
platform_surface = config.small_font.render(platform_text, True, color) platform_surface = config.small_font.render(platform_text, True, color)
@@ -697,31 +721,41 @@ def draw_progress_screen(screen):
# Écran popup résultat téléchargement # Écran popup résultat téléchargement
def draw_popup_result_download(screen, message, is_error): def draw_popup_result_download(screen, message, is_error):
"""Affiche une popup avec un message de résultat.""" """Affiche une popup flottante centrée avec un message de résultat."""
screen.blit(OVERLAY, (0, 0))
if message is None: if message is None:
message = "Téléchargement annulé par l'utilisateur." message = "Téléchargement annulé par l'utilisateur."
logger.debug(f"Message popup : {message}, is_error={is_error}") logger.debug(f"Message popup : {message}, is_error={is_error}")
# Réduire la largeur maximale pour le wrapping # Réduire la largeur maximale pour le wrapping
wrapped_message = wrap_text(message, config.small_font, config.screen_width - 160) max_popup_width = config.screen_width // 3 # Popup prend 1/3 de la largeur de l'écran
# Débogage pour vérifier les lignes wrappées wrapped_message = wrap_text(message, config.small_font, max_popup_width - 40) # 40 pixels de marge interne
logger.debug(f"Lignes wrappées : {wrapped_message}")
line_height = config.small_font.get_height() + 5 line_height = config.small_font.get_height() + 5
text_height = len(wrapped_message) * line_height text_height = len(wrapped_message) * line_height
margin_top_bottom = 20 margin_top_bottom = 15
margin_sides = 20
rect_height = text_height + 2 * margin_top_bottom rect_height = text_height + 2 * margin_top_bottom
max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=300) max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=200)
rect_width = max_text_width + 100 # Augmenter la marge rect_width = min(max_text_width + 2 * margin_sides, max_popup_width)
# Positionner la popup au centre de l'écran
rect_x = (config.screen_width - rect_width) // 2 rect_x = (config.screen_width - rect_width) // 2
rect_y = (config.screen_height - rect_height) // 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) # Fond semi-transparent pour un effet flottant
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) popup_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA)
# Utiliser button_idle sans modifier son alpha (déjà à 150)
pygame.draw.rect(popup_surface, THEME_COLORS["button_idle"], (0, 0, rect_width, rect_height), border_radius=10)
# Bordure sans alpha modifié
pygame.draw.rect(popup_surface, THEME_COLORS["border"], (0, 0, rect_width, rect_height), 2, border_radius=10)
# Afficher le texte
for i, line in enumerate(wrapped_message): for i, line in enumerate(wrapped_message):
text = config.small_font.render(line, True, THEME_COLORS["error_text"] if is_error else THEME_COLORS["fond_lignes"]) text = config.small_font.render(line, True, THEME_COLORS["error_text"] if is_error else THEME_COLORS["text"])
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) text_rect = text.get_rect(center=(rect_width // 2, margin_top_bottom + i * line_height + line_height // 2))
screen.blit(text, text_rect) popup_surface.blit(text, text_rect)
# Appliquer la surface de la popup sur l'écran
screen.blit(popup_surface, (rect_x, rect_y))
# Écran avertissement extension non supportée téléchargement # Écran avertissement extension non supportée téléchargement
def draw_extension_warning(screen): def draw_extension_warning(screen):
@@ -795,7 +829,9 @@ def draw_extension_warning(screen):
def draw_controls(screen, menu_state): def draw_controls(screen, menu_state):
"""Affiche les contrôles sur une seule ligne en bas de lécran.""" """Affiche les contrôles sur une seule ligne en bas de lécran."""
start_button = get_control_display('start', 'START') start_button = get_control_display('start', 'START')
control_text = f"RGSX v{config.app_version} - {start_button} : Options - History - Help" history_button = get_control_display('history', 'H')
filter_button = get_control_display('filter', 'F')
control_text = f"RGSX v{config.app_version} - {start_button} : Options - {history_button}: Historique - {filter_button} : Filtrer (bug)"
max_width = config.screen_width - 40 max_width = config.screen_width - 40
wrapped_controls = wrap_text(control_text, config.small_font, max_width) wrapped_controls = wrap_text(control_text, config.small_font, max_width)
line_height = config.small_font.get_height() + 5 line_height = config.small_font.get_height() + 5

View File

@@ -2,6 +2,7 @@ import json
import os import os
import logging import logging
import config import config
from datetime import datetime
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -15,26 +16,30 @@ def init_history():
if not os.path.exists(history_path): if not os.path.exists(history_path):
try: try:
os.makedirs(os.path.dirname(history_path), exist_ok=True) os.makedirs(os.path.dirname(history_path), exist_ok=True)
with open(history_path, "w") as f: with open(history_path, "w", encoding='utf-8') as f:
json.dump([], f) # Initialise avec une liste vide json.dump([], f) # Initialise avec une liste vide
logger.info(f"Fichier d'historique créé : {history_path}") logger.info(f"Fichier d'historique créé : {history_path}")
except OSError as e: except OSError as e:
logger.error(f"Erreur lors de la création du fichier d'historique : {e}") logger.error(f"Erreur lors de la création du fichier d'historique : {e}")
else: else:
logger.info(f"Fichier d'historique trouvé : {history_path}") logger.info(f"Fichier d'historique trouvé : {history_path}")
return history_path return history_path
def load_history(): def load_history():
"""Charge l'historique depuis history.json.""" """Charge l'historique depuis history.json."""
history_path = getattr(config, 'HISTORY_PATH', DEFAULT_HISTORY_PATH) history_path = getattr(config, 'HISTORY_PATH', DEFAULT_HISTORY_PATH)
try: try:
with open(history_path, "r") as f: if not os.path.exists(history_path):
logger.debug(f"Aucun fichier d'historique trouvé à {history_path}")
return []
with open(history_path, "r", encoding='utf-8') as f:
history = json.load(f) history = json.load(f)
# Valider la structure : liste de dictionnaires avec 'platform', 'game_name', 'status' # Valider la structure : liste de dictionnaires avec 'platform', 'game_name', 'status'
for entry in history: for entry in history:
if not all(key in entry for key in ['platform', 'game_name', 'status']): if not all(key in entry for key in ['platform', 'game_name', 'status']):
logger.warning(f"Entrée d'historique invalide : {entry}") logger.warning(f"Entrée d'historique invalide : {entry}")
return [] return []
logger.debug(f"Historique chargé depuis {history_path}, {len(history)} entrées")
return history return history
except (FileNotFoundError, json.JSONDecodeError) as e: except (FileNotFoundError, json.JSONDecodeError) as e:
logger.error(f"Erreur lors de la lecture de {history_path} : {e}") logger.error(f"Erreur lors de la lecture de {history_path} : {e}")
@@ -44,28 +49,36 @@ def save_history(history):
"""Sauvegarde l'historique dans history.json.""" """Sauvegarde l'historique dans history.json."""
history_path = getattr(config, 'HISTORY_PATH', DEFAULT_HISTORY_PATH) history_path = getattr(config, 'HISTORY_PATH', DEFAULT_HISTORY_PATH)
try: try:
with open(history_path, "w") as f: os.makedirs(os.path.dirname(history_path), exist_ok=True)
json.dump(history, f, indent=2) with open(history_path, "w", encoding='utf-8') as f:
json.dump(history, f, indent=2, ensure_ascii=False)
logger.debug(f"Historique sauvegardé dans {history_path}") logger.debug(f"Historique sauvegardé dans {history_path}")
except Exception as e: except Exception as e:
logger.error(f"Erreur lors de l'écriture de {history_path} : {e}") logger.error(f"Erreur lors de l'écriture de {history_path} : {e}")
def add_to_history(platform, game_name, status): def add_to_history(platform, game_name, status, url=None, progress=0, message=None, timestamp=None):
"""Ajoute une entrée à l'historique.""" """Ajoute une entrée à l'historique."""
history = load_history() history = load_history()
history.append({ entry = {
"platform": platform, "platform": platform,
"game_name": game_name, "game_name": game_name,
"status": status "status": status,
}) "url": url,
"progress": progress,
"timestamp": timestamp or datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
if message:
entry["message"] = message
history.append(entry)
save_history(history) save_history(history)
logger.info(f"Ajout à l'historique : platform={platform}, game_name={game_name}, status={status}") logger.info(f"Ajout à l'historique : platform={platform}, game_name={game_name}, status={status}, progress={progress}")
return entry
def clear_history(): def clear_history():
"""Vide l'historique.""" """Vide l'historique."""
history_path = getattr(config, 'HISTORY_PATH', DEFAULT_HISTORY_PATH) history_path = getattr(config, 'HISTORY_PATH', DEFAULT_HISTORY_PATH)
try: try:
with open(history_path, "w") as f: with open(history_path, "w", encoding='utf-8') as f:
json.dump([], f) json.dump([], f)
logger.info(f"Historique vidé : {history_path}") logger.info(f"Historique vidé : {history_path}")
except Exception as e: except Exception as e:

View File

@@ -8,8 +8,10 @@ import asyncio
import config import config
from config import OTA_VERSION_ENDPOINT, OTA_UPDATE_SCRIPT from config import OTA_VERSION_ENDPOINT, OTA_UPDATE_SCRIPT
from utils import sanitize_filename, extract_zip, extract_rar from utils import sanitize_filename, extract_zip, extract_rar
from history import add_to_history, load_history from history import save_history
import logging import logging
import queue
import time
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -118,13 +120,26 @@ async def check_for_updates():
# File d'attente pour la progression
import queue
progress_queue = queue.Queue()
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}")
async def download_rom(url, platform, game_name, is_zip_non_supported=False, task_id=None):
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] 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
def download_thread(): def download_thread():
logger.debug(f"Thread téléchargement démarré pour {url}") logger.debug(f"Thread téléchargement démarré pour {url}, task_id={task_id}")
try: try:
dest_dir = None dest_dir = None
for platform_dict in config.platform_dicts: for platform_dict in config.platform_dicts:
@@ -132,10 +147,8 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False):
dest_dir = platform_dict.get("folder") dest_dir = platform_dict.get("folder")
break break
if not dest_dir: if not dest_dir:
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}") dest_dir = os.path.join("/userdata/roms", platform.lower().replace(" ", ""))
dest_dir = os.path.join("/userdata/roms", platform)
logger.debug(f"Vérification répertoire destination: {dest_dir}")
os.makedirs(dest_dir, exist_ok=True) os.makedirs(dest_dir, exist_ok=True)
if not os.access(dest_dir, os.W_OK): if not os.access(dest_dir, os.W_OK):
raise PermissionError(f"Pas de permission d'écriture dans {dest_dir}") raise PermissionError(f"Pas de permission d'écriture dans {dest_dir}")
@@ -144,118 +157,106 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False):
dest_path = os.path.join(dest_dir, f"{sanitized_name}") dest_path = os.path.join(dest_dir, f"{sanitized_name}")
logger.debug(f"Chemin destination: {dest_path}") logger.debug(f"Chemin destination: {dest_path}")
lock = threading.Lock()
with lock:
config.download_progress[url] = {
"downloaded_size": 0,
"total_size": 0,
"status": "Téléchargement",
"progress_percent": 0,
"game_name": game_name
}
config.needs_redraw = True # Forcer le redraw
logger.debug(f"Progression initialisée pour {url}")
headers = {'User-Agent': 'Mozilla/5.0'} headers = {'User-Agent': 'Mozilla/5.0'}
logger.debug(f"Envoi requête GET à {url}")
response = requests.get(url, stream=True, headers=headers, timeout=30) response = requests.get(url, stream=True, headers=headers, timeout=30)
logger.debug(f"Réponse reçue, status: {response.status_code}")
response.raise_for_status() response.raise_for_status()
total_size = int(response.headers.get('content-length', 0)) total_size = int(response.headers.get('content-length', 0))
logger.debug(f"Taille totale: {total_size} octets") logger.debug(f"Taille totale: {total_size} octets")
with lock:
config.download_progress[url]["total_size"] = total_size # Initialiser la progression avec task_id
config.needs_redraw = True # Forcer le redraw progress_queue.put((task_id, 0, total_size))
logger.debug(f"Progression initiale envoyée: 0% pour {game_name}, task_id={task_id}")
downloaded = 0 downloaded = 0
chunk_size = 4096
last_update_time = time.time()
update_interval = 0.5 # Mettre à jour toutes les 0,5 secondes
with open(dest_path, 'wb') as f: with open(dest_path, 'wb') as f:
logger.debug(f"Ouverture fichier: {dest_path}") for chunk in response.iter_content(chunk_size=chunk_size):
for chunk in response.iter_content(chunk_size=8192):
if chunk: if chunk:
size_received = len(chunk)
f.write(chunk) f.write(chunk)
downloaded += len(chunk) downloaded += size_received
with lock: current_time = time.time()
config.download_progress[url]["downloaded_size"] = downloaded if current_time - last_update_time >= update_interval:
config.download_progress[url]["status"] = "Téléchargement" progress = (downloaded / total_size * 100) if total_size > 0 else 0
config.download_progress[url]["progress_percent"] = (downloaded / total_size * 100) if total_size > 0 else 0 progress_queue.put((task_id, downloaded, total_size))
config.needs_redraw = True # Forcer le redraw # logger.debug(f"Progress update sent: {progress:.1f}% for {game_name}, task_id={task_id}")
#logger.debug(f"Progression: {downloaded}/{total_size} octets, {config.download_progress[url]['progress_percent']:.1f}%") last_update_time = current_time
else:
logger.debug("Chunk vide reçu")
if is_zip_non_supported: os.chmod(dest_path, 0o644)
with lock: logger.debug(f"Téléchargement terminé: {dest_path}")
config.download_progress[url]["downloaded_size"] = 0 result[0] = True
config.download_progress[url]["total_size"] = 0 result[1] = f"Download_OK: {game_name}"
config.download_progress[url]["status"] = "Extracting"
config.download_progress[url]["progress_percent"] = 0
config.needs_redraw = True # Forcer le redraw
extension = os.path.splitext(dest_path)[1].lower()
if extension == ".zip":
success, msg = extract_zip(dest_path, dest_dir, url)
elif extension == ".rar":
success, msg = extract_rar(dest_path, dest_dir, url)
else:
raise Exception(f"Type d'archive non supporté: {extension}")
if not success:
raise Exception(f"Échec de l'extraction de l'archive: {msg}")
result[0] = True
result[1] = f"Downloaded / extracted : {game_name}"
else:
os.chmod(dest_path, 0o644)
logger.debug(f"Téléchargement terminé: {dest_path}")
result[0] = True
result[1] = f"Download_OK : {game_name}"
except Exception as e: except Exception as e:
logger.error(f"Erreur téléchargement {url}: {str(e)}") logger.error(f"Erreur téléchargement {url}: {str(e)}")
if url in config.download_progress:
with lock:
del config.download_progress[url]
if os.path.exists(dest_path):
os.remove(dest_path)
result[0] = False result[0] = False
result[1] = f"Erreur téléchargement {game_name}" result[1] = f"Erreur téléchargement {game_name}: {str(e)}"
finally: finally:
logger.debug(f"Thread téléchargement terminé pour {url}") logger.debug(f"Thread téléchargement terminé pour {url}, task_id={task_id}")
with lock: progress_queue.put((task_id, result[0], result[1]))
config.download_result_message = result[1] logger.debug(f"Final result sent to queue: success={result[0]}, message={result[1]}, task_id={task_id}")
config.download_result_error = not result[0]
config.download_result_start_time = pygame.time.get_ticks()
config.menu_state = "download_result"
config.needs_redraw = True # Forcer le redraw
# Enregistrement dans l'historique
add_to_history(platform, game_name, "OK" if result[0] else "Error")
config.history = load_history() # Recharger l'historique
logger.debug(f"Enregistrement dans l'historique: platform={platform}, game_name={game_name}, status={'Download_OK' if result[0] else 'Erreur'}")
thread = threading.Thread(target=download_thread) thread = threading.Thread(target=download_thread)
logger.debug(f"Démarrage thread pour {url}")
thread.start() thread.start()
while thread.is_alive():
pygame.event.pump()
await asyncio.sleep(0.1)
thread.join()
logger.debug(f"Thread rejoint pour {url}")
# 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]
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
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 = (downloaded / total_size * 100) if total_size > 0 else 0
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["progress"] = progress
entry["status"] = "Téléchargement"
config.needs_redraw = True
# logger.debug(f"Progress updated in history: {progress:.1f}% for {game_name}, task_id={task_id}")
break
await asyncio.sleep(0.2)
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}")
return result[0], result[1] return result[0], result[1]
async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False, task_id=None):
def is_1fichier_url(url): 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}")
"""Détecte si l'URL est un lien 1fichier."""
return "1fichier.com" in url
def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False):
"""Télécharge un fichier depuis 1fichier en utilisant l'API officielle."""
logger.debug(f"Début téléchargement 1fichier: {game_name} depuis {url}, is_zip_non_supported={is_zip_non_supported}")
result = [None, None] result = [None, None]
def download_thread(): # Vider la file d'attente avant de commencer
logger.debug(f"Thread téléchargement 1fichier démarré pour {url}") while not progress_queue.empty():
try: try:
# Nettoyer l'URL progress_queue.get_nowait()
link = url.split('&af=')[0] logger.debug(f"File progress_queue vidée pour {game_name}")
except queue.Empty:
break
# Déterminer le répertoire de destination 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]
dest_dir = None dest_dir = None
for platform_dict in config.platform_dicts: for platform_dict in config.platform_dicts:
if platform_dict["platform"] == platform: if platform_dict["platform"] == platform:
@@ -270,7 +271,6 @@ def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False)
if not os.access(dest_dir, os.W_OK): if not os.access(dest_dir, os.W_OK):
raise PermissionError(f"Pas de permission d'écriture dans {dest_dir}") raise PermissionError(f"Pas de permission d'écriture dans {dest_dir}")
# Préparer les en-têtes et le payload
headers = { headers = {
"Authorization": f"Bearer {config.API_KEY_1FICHIER}", "Authorization": f"Bearer {config.API_KEY_1FICHIER}",
"Content-Type": "application/json" "Content-Type": "application/json"
@@ -280,7 +280,6 @@ def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False)
"pretty": 1 "pretty": 1
} }
# Étape 1 : Obtenir les informations du fichier
logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/file/info.cgi pour {url}") logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/file/info.cgi pour {url}")
response = requests.post("https://api.1fichier.com/v1/file/info.cgi", headers=headers, json=payload, timeout=30) 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 reçue, status: {response.status_code}")
@@ -304,7 +303,6 @@ def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False)
dest_path = os.path.join(dest_dir, sanitized_filename) dest_path = os.path.join(dest_dir, sanitized_filename)
logger.debug(f"Chemin destination: {dest_path}") logger.debug(f"Chemin destination: {dest_path}")
# Étape 2 : Obtenir le jeton de téléchargement
logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/download/get_token.cgi pour {url}") logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/download/get_token.cgi pour {url}")
response = requests.post("https://api.1fichier.com/v1/download/get_token.cgi", headers=headers, json=payload, timeout=30) 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 reçue, status: {response.status_code}")
@@ -318,22 +316,12 @@ def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False)
result[1] = "Impossible de récupérer l'URL de téléchargement" result[1] = "Impossible de récupérer l'URL de téléchargement"
return return
# Étape 3 : Initialiser la progression
lock = threading.Lock() lock = threading.Lock()
with lock:
config.download_progress[url] = {
"downloaded_size": 0,
"total_size": 0,
"status": "Téléchargement",
"progress_percent": 0,
"game_name": game_name
}
config.needs_redraw = True
logger.debug(f"Progression initialisée pour {url}")
# Étape 4 : Télécharger le fichier
retries = 10 retries = 10
retry_delay = 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}")
for attempt in range(retries): for attempt in range(retries):
try: try:
logger.debug(f"Tentative {attempt + 1} : Envoi requête GET à {final_url}") logger.debug(f"Tentative {attempt + 1} : Envoi requête GET à {final_url}")
@@ -343,31 +331,44 @@ def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False)
total_size = int(response.headers.get('content-length', 0)) total_size = int(response.headers.get('content-length', 0))
logger.debug(f"Taille totale: {total_size} octets") logger.debug(f"Taille totale: {total_size} octets")
with lock: with lock:
config.download_progress[url]["total_size"] = total_size for entry in config.history:
config.needs_redraw = True if 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
downloaded = 0 downloaded = 0
chunk_size = 8192
last_update_time = time.time()
update_interval = 0.5 # Mettre à jour toutes les 0,5 secondes
with open(dest_path, 'wb') as f: with open(dest_path, 'wb') as f:
logger.debug(f"Ouverture fichier: {dest_path}") logger.debug(f"Ouverture fichier: {dest_path}")
for chunk in response.iter_content(chunk_size=8192): for chunk in response.iter_content(chunk_size=chunk_size):
if chunk: if chunk:
f.write(chunk) f.write(chunk)
downloaded += len(chunk) downloaded += len(chunk)
with lock: current_time = time.time()
config.download_progress[url]["downloaded_size"] = downloaded if current_time - last_update_time >= update_interval:
config.download_progress[url]["status"] = "Téléchargement" with lock:
config.download_progress[url]["progress_percent"] = (downloaded / total_size * 100) if total_size > 0 else 0 for entry in config.history:
config.needs_redraw = True if entry["url"] == url and entry["status"] == "downloading":
#logger.debug(f"Progression: {downloaded}/{total_size} octets, {config.download_progress[url]['progress_percent']:.1f}%") entry["progress"] = (downloaded / total_size * 100) if total_size > 0 else 0
entry["status"] = "Téléchargement"
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))
last_update_time = current_time
# Étape 5 : Extraire si nécessaire
if is_zip_non_supported: if is_zip_non_supported:
with lock: with lock:
config.download_progress[url]["downloaded_size"] = 0 for entry in config.history:
config.download_progress[url]["total_size"] = 0 if entry["url"] == url and entry["status"] == "Téléchargement":
config.download_progress[url]["status"] = "Extracting" entry["progress"] = 0
config.download_progress[url]["progress_percent"] = 0 entry["status"] = "Extracting"
config.needs_redraw = True config.needs_redraw = True
break
extension = os.path.splitext(dest_path)[1].lower() extension = os.path.splitext(dest_path)[1].lower()
if extension == ".zip": if extension == ".zip":
success, msg = extract_zip(dest_path, dest_dir, url) success, msg = extract_zip(dest_path, dest_dir, url)
@@ -389,7 +390,6 @@ def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False)
except requests.exceptions.RequestException as e: 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: if attempt < retries - 1:
import time
time.sleep(retry_delay) time.sleep(retry_delay)
else: else:
logger.error("Nombre maximum de tentatives atteint") logger.error("Nombre maximum de tentatives atteint")
@@ -400,25 +400,57 @@ def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.error(f"Erreur API 1fichier : {e}") logger.error(f"Erreur API 1fichier : {e}")
result[0] = False result[0] = False
result[1] = f"Erreur lors de la requête API, la clé est peut etre incorrecte: {str(e)}" result[1] = f"Erreur lors de la requête API, la clé est peuttre incorrecte: {str(e)}"
finally: finally:
logger.debug(f"Thread téléchargement 1fichier terminé pour {url}") logger.debug(f"Thread téléchargement 1fichier terminé pour {url}, task_id={task_id}")
with lock: progress_queue.put((task_id, result[0], result[1]))
config.download_result_message = result[1] logger.debug(f"Final result sent to queue: success={result[0]}, message={result[1]}, task_id={task_id}")
config.download_result_error = not result[0]
config.download_result_start_time = pygame.time.get_ticks()
config.menu_state = "download_result"
config.needs_redraw = True
# Enregistrement dans l'historique
add_to_history(platform, game_name, "Download_OK" if result[0] else "Erreur")
config.history = load_history()
logger.debug(f"Enregistrement dans l'historique: platform={platform}, game_name={game_name}, status={'Download_OK' if result[0] else 'Erreur'}")
thread = threading.Thread(target=download_thread) thread = threading.Thread(target=download_thread)
logger.debug(f"Démarrage thread pour {url}") logger.debug(f"Démarrage thread pour {url}, task_id={task_id}")
thread.start() thread.start()
thread.join()
logger.debug(f"Thread rejoint pour {url}")
return result[0], result[1] # 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]
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
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 = (downloaded / total_size * 100) if total_size > 0 else 0
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
entry["progress"] = progress
entry["status"] = "Téléchargement"
config.needs_redraw = True
# logger.debug(f"Progress updated in history: {progress:.1f}% for {game_name}, task_id={task_id}")
break
await asyncio.sleep(0.2)
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}")
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

@@ -9,8 +9,10 @@ import subprocess
import config import config
import threading import threading
import zipfile import zipfile
import time
import random import random
from config import JSON_EXTENSIONS from config import JSON_EXTENSIONS
from history import save_history
from datetime import datetime from datetime import datetime
@@ -177,11 +179,12 @@ def write_unavailable_systems():
except Exception as e: except Exception as e:
logger.error(f"Erreur lors de l'écriture du fichier {log_file} : {str(e)}") logger.error(f"Erreur lors de l'écriture du fichier {log_file} : {str(e)}")
def truncate_text_middle(text, font, max_width, is_filename=True):
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.
"""Tronque le texte en insérant '...' au milieu, en préservant le début et la fin, sans extension de fichier.""" Si is_filename=False, ne supprime pas l'extension."""
# Supprimer l'extension de fichier # Supprimer l'extension uniquement si is_filename est True
text = text.rsplit('.', 1)[0] if '.' in text else text if is_filename:
text = text.rsplit('.', 1)[0] if '.' in text else text
text_width = font.size(text)[0] text_width = font.size(text)[0]
if text_width <= max_width: if text_width <= max_width:
return text return text
@@ -191,7 +194,7 @@ def truncate_text_middle(text, font, max_width):
if max_text_width <= 0: if max_text_width <= 0:
return ellipsis return ellipsis
# Diviser la largeur disponible entre début et fin # Diviser la largeur disponible entre début et fin, en priorisant la fin
chars = list(text) chars = list(text)
left = [] left = []
right = [] right = []
@@ -200,14 +203,9 @@ def truncate_text_middle(text, font, max_width):
left_idx = 0 left_idx = 0
right_idx = len(chars) - 1 right_idx = len(chars) - 1
# Préserver plus de caractères à droite pour garder le '%'
while left_idx <= right_idx and (left_width + right_width) < max_text_width: while left_idx <= right_idx and (left_width + right_width) < max_text_width:
if left_idx < right_idx: # Ajouter à droite en priorité
left.append(chars[left_idx])
left_width = font.size(''.join(left))[0]
if left_width + right_width > max_text_width:
left.pop()
break
left_idx += 1
if left_idx <= right_idx: if left_idx <= right_idx:
right.insert(0, chars[right_idx]) right.insert(0, chars[right_idx])
right_width = font.size(''.join(right))[0] right_width = font.size(''.join(right))[0]
@@ -215,6 +213,14 @@ def truncate_text_middle(text, font, max_width):
right.pop(0) right.pop(0)
break break
right_idx -= 1 right_idx -= 1
# Ajouter à gauche seulement si nécessaire
if left_idx < right_idx:
left.append(chars[left_idx])
left_width = font.size(''.join(left))[0]
if left_width + right_width > max_text_width:
left.pop()
break
left_idx += 1
# Reculer jusqu'à un espace pour éviter de couper un mot # Reculer jusqu'à un espace pour éviter de couper un mot
while left and left[-1] != ' ' and left_width + right_width > max_text_width: while left and left[-1] != ' ' and left_width + right_width > max_text_width:
@@ -305,12 +311,13 @@ def load_system_image(platform_dict):
return None return None
# Fonction pour extraire le contenu d'un fichier ZIP
def extract_zip(zip_path, dest_dir, url): 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.""" """Extrait le contenu du fichier ZIP dans le dossier cible avec un suivi progressif de la progression."""
logger.debug(f"Extraction de {zip_path} dans {dest_dir}")
try: try:
lock = threading.Lock() lock = threading.Lock()
with zipfile.ZipFile(zip_path, 'r') as zip_ref: with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.testzip() # Vérifier l'intégrité de l'archive
total_size = sum(info.file_size for info in zip_ref.infolist() if not info.is_dir()) 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") logger.info(f"Taille totale à extraire: {total_size} octets")
if total_size == 0: if total_size == 0:
@@ -319,7 +326,9 @@ def extract_zip(zip_path, dest_dir, url):
extracted_size = 0 extracted_size = 0
os.makedirs(dest_dir, exist_ok=True) os.makedirs(dest_dir, exist_ok=True)
chunk_size = 8192 chunk_size = 2048 # Réduire pour plus de mises à jour
last_save_time = time.time()
save_interval = 0.5 # Sauvegarder toutes les 0.5 secondes
for info in zip_ref.infolist(): for info in zip_ref.infolist():
if info.is_dir(): if info.is_dir():
continue continue
@@ -335,15 +344,19 @@ def extract_zip(zip_path, dest_dir, url):
dest.write(chunk) dest.write(chunk)
file_extracted += len(chunk) file_extracted += len(chunk)
extracted_size += len(chunk) extracted_size += len(chunk)
current_time = time.time()
with lock: with lock:
config.download_progress[url]["downloaded_size"] = extracted_size for entry in config.history:
config.download_progress[url]["total_size"] = total_size if entry["url"] == url and entry["status"] in ["Téléchargement", "Extracting"]:
config.download_progress[url]["status"] = "Extracting" entry["status"] = "Extracting"
config.download_progress[url]["progress_percent"] = (extracted_size / total_size * 100) if total_size > 0 else 0 entry["progress"] = (extracted_size / total_size * 100) if total_size > 0 else 0
config.needs_redraw = True # Forcer le redraw entry["message"] = "Extraction en cours"
# Logger une seule ligne à la fin de l'extraction du fichier if current_time - last_save_time >= save_interval:
progress_percentage = (extracted_size / total_size * 100) if total_size > 0 else 0 save_history(config.history)
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}%") 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={entry['progress']:.1f}%")
config.needs_redraw = True
break
os.chmod(file_path, 0o644) os.chmod(file_path, 0o644)
for root, dirs, _ in os.walk(dest_dir): for root, dirs, _ in os.walk(dest_dir):
@@ -352,10 +365,17 @@ def extract_zip(zip_path, dest_dir, url):
os.remove(zip_path) os.remove(zip_path)
logger.info(f"Fichier ZIP {zip_path} extrait dans {dest_dir} et supprimé") logger.info(f"Fichier ZIP {zip_path} extrait dans {dest_dir} et supprimé")
return True, "ZIP extrait avec succès" return True, f"Extracted: {os.path.basename(zip_path)}"
except zipfile.BadZipFile as e:
logger.error(f"Erreur: Archive ZIP corrompue: {str(e)}")
return False, f"Archive ZIP corrompue: {str(e)}"
except PermissionError as e:
logger.error(f"Erreur: Permission refusée lors de l'extraction: {str(e)}")
return False, f"Permission refusée lors de l'extraction: {str(e)}"
except Exception as e: except Exception as e:
logger.error(f"Erreur lors de l'extraction de {zip_path}: {e}") logger.error(f"Erreur lors de l'extraction de {zip_path}: {str(e)}")
return False, str(e) return False, f"Échec de l'extraction: {str(e)}"
# Fonction pour extraire le contenu d'un fichier RAR # Fonction pour extraire le contenu d'un fichier RAR
def extract_rar(rar_path, dest_dir, url): def extract_rar(rar_path, dest_dir, url):