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 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, THEME_COLORS
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates
from controls import handle_controls, validate_menu_state
from controls_mapper import load_controls_config, map_controls, draw_controls_mapping, ACTIONS
from utils import 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
from config import OTA_VERSION_ENDPOINT, OTA_UPDATE_SCRIPT, OTA_data_ZIP
@@ -132,7 +134,7 @@ async def main():
clock = pygame.time.Clock()
while running:
clock.tick(60) # Limite à 60 FPS
clock.tick(30) # Limite à 60 FPS
if config.update_triggered:
logger.debug("Mise à jour déclenchée, arrêt de la boucle principale")
break
@@ -143,6 +145,12 @@ async def main():
if config.menu_state == "download_progress" and current_time - last_redraw_time >= 100:
config.needs_redraw = True
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
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}")
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)
config.needs_redraw = True
if action == "quit":
@@ -227,10 +268,20 @@ async def main():
elif action == "download" and config.menu_state == "game" and config.filtered_games:
game = config.filtered_games[config.current_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
if 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 not config.API_KEY_1FICHIER:
config.previous_menu_state = config.menu_state
@@ -238,6 +289,11 @@ async def main():
config.error_message = (
"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
logger.error("Clé API 1fichier absente")
config.pending_download = None
@@ -249,18 +305,20 @@ async def main():
config.extension_confirm_selection = 0
config.needs_redraw = True
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:
config.previous_menu_state = config.menu_state
logger.debug(f"Previous menu state défini: {config.previous_menu_state}")
success, message = download_from_1fichier(url, platform, game_name, is_zip_non_supported)
config.download_result_message = message
config.download_result_error = not success
config.download_result_start_time = pygame.time.get_ticks()
config.menu_state = "download_result"
config.download_progress.clear()
config.pending_download = None
# Lancer le téléchargement dans une tâche asynchrone
task_id = str(pygame.time.get_ticks())
config.download_tasks[task_id] = (
asyncio.create_task(download_from_1fichier(url, platform, game_name, is_zip_non_supported)),
url, game_name, platform
)
config.menu_state = "history" # Passer à l'historique
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:
is_supported, message, is_zip_non_supported = check_extension_before_download(url, platform, game_name)
if not is_supported:
@@ -269,18 +327,20 @@ async def main():
config.extension_confirm_selection = 0
config.needs_redraw = True
logger.debug(f"Extension non reconnue, passage à extension_warning pour {game_name}")
# Supprimer l'entrée temporaire si erreur
config.history.pop()
else:
config.previous_menu_state = config.menu_state
logger.debug(f"Previous menu state défini: {config.previous_menu_state}")
success, message = download_rom(url, platform, game_name, is_zip_non_supported)
config.download_result_message = message
config.download_result_error = not success
config.download_result_start_time = pygame.time.get_ticks()
config.menu_state = "download_result"
config.download_progress.clear()
config.pending_download = None
# Lancer le téléchargement dans une tâche asynchrone
task_id = str(pygame.time.get_ticks())
config.download_tasks[task_id] = (
asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported)),
url, game_name, platform
)
config.menu_state = "history" # Passer à l'historique
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:
entry = config.history[config.current_history_item]
platform = entry["platform"]
@@ -340,13 +400,27 @@ async def main():
config.needs_redraw = True
logger.debug(f"Retéléchargement terminé pour {game_name}, succès={success}, message={message}")
break
# Gestion des téléchargements
if config.download_tasks:
for task_id, (task, url, game_name, platform) in list(config.download_tasks.items()):
if task.done():
try:
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_error = not success
config.download_result_start_time = pygame.time.get_ticks()
@@ -355,9 +429,59 @@ async def main():
config.pending_download = None
config.needs_redraw = True
del config.download_tasks[task_id]
logger.debug(f"Téléchargement terminé: {game_name}, succès={success}, message={message}")
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_start_time = pygame.time.get_ticks()
config.menu_state = "download_result"
@@ -365,19 +489,20 @@ async def main():
config.pending_download = None
config.needs_redraw = True
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
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.pending_download = None
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
if config.needs_redraw:
draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"])
if config.menu_state == "controls_mapping":
draw_controls_mapping(screen, ACTIONS[0], None, False, 0.0)
elif config.menu_state == "loading":
@@ -406,7 +531,8 @@ async def main():
elif config.menu_state == "controls_help":
draw_controls_help(screen, config.previous_menu_state)
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":
draw_clear_history_dialog(screen)
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")
draw_controls(screen, config.menu_state)
pygame.display.flip()
config.needs_redraw = False
# logger.debug("Screen flipped with pygame.display.flip()")
# Gestion de l'état controls_mapping
if config.menu_state == "controls_mapping":

Binary file not shown.

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import pygame # type: ignore
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 math
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)
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_count = len(history)
col_platform_width = int((0.95 * config.screen_width - 60) * 0.33)
col_game_width = int((0.95 * config.screen_width - 60) * 0.50)
col_status_width = int((0.95 * config.screen_width - 60) * 0.17)
# Define column widths as percentages of available space
column_width_percentages = {
"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)
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))
screen.blit(text_surface, text_rect)
# Ajouter un séparateur sous les en-têtes
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)
@@ -523,10 +529,28 @@ def draw_history_list(screen):
platform = entry.get("platform", "Inconnu")
game_name = entry.get("game_name", "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"]
platform_text = truncate_text_middle(platform, config.small_font, col_platform_width - 10)
game_text = truncate_text_middle(game_name, config.small_font, col_game_width - 10)
status_text = truncate_text_middle(status, config.small_font, col_status_width - 10)
platform_text = truncate_text_end(platform, config.small_font, col_platform_width - 10)
game_text = truncate_text_end(game_name, config.small_font, col_game_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
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
def draw_popup_result_download(screen, message, is_error):
"""Affiche une popup avec un message de résultat."""
screen.blit(OVERLAY, (0, 0))
"""Affiche une popup flottante centrée avec un message de résultat."""
if message is None:
message = "Téléchargement annulé par l'utilisateur."
logger.debug(f"Message popup : {message}, is_error={is_error}")
# Réduire la largeur maximale pour le wrapping
wrapped_message = wrap_text(message, config.small_font, config.screen_width - 160)
# Débogage pour vérifier les lignes wrappées
logger.debug(f"Lignes wrappées : {wrapped_message}")
max_popup_width = config.screen_width // 3 # Popup prend 1/3 de la largeur de l'écran
wrapped_message = wrap_text(message, config.small_font, max_popup_width - 40) # 40 pixels de marge interne
line_height = config.small_font.get_height() + 5
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
max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=300)
rect_width = max_text_width + 100 # Augmenter la marge
max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=200)
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_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)
# Fond semi-transparent pour un effet flottant
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):
text = config.small_font.render(line, True, THEME_COLORS["error_text"] if is_error else THEME_COLORS["fond_lignes"])
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)
text = config.small_font.render(line, True, THEME_COLORS["error_text"] if is_error else THEME_COLORS["text"])
text_rect = text.get_rect(center=(rect_width // 2, margin_top_bottom + i * line_height + line_height // 2))
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
def draw_extension_warning(screen):
@@ -795,7 +829,9 @@ def draw_extension_warning(screen):
def draw_controls(screen, menu_state):
"""Affiche les contrôles sur une seule ligne en bas de lécran."""
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
wrapped_controls = wrap_text(control_text, config.small_font, max_width)
line_height = config.small_font.get_height() + 5

View File

@@ -2,6 +2,7 @@ import json
import os
import logging
import config
from datetime import datetime
logger = logging.getLogger(__name__)
@@ -15,26 +16,30 @@ def init_history():
if not os.path.exists(history_path):
try:
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
logger.info(f"Fichier d'historique créé : {history_path}")
except OSError as e:
logger.error(f"Erreur lors de la création du fichier d'historique : {e}")
else:
logger.info(f"Fichier d'historique trouvé : {history_path}")
return history_path
return history_path
def load_history():
"""Charge l'historique depuis history.json."""
history_path = getattr(config, 'HISTORY_PATH', DEFAULT_HISTORY_PATH)
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)
# Valider la structure : liste de dictionnaires avec 'platform', 'game_name', 'status'
for entry in history:
if not all(key in entry for key in ['platform', 'game_name', 'status']):
logger.warning(f"Entrée d'historique invalide : {entry}")
return []
logger.debug(f"Historique chargé depuis {history_path}, {len(history)} entrées")
return history
except (FileNotFoundError, json.JSONDecodeError) as 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."""
history_path = getattr(config, 'HISTORY_PATH', DEFAULT_HISTORY_PATH)
try:
with open(history_path, "w") as f:
json.dump(history, f, indent=2)
os.makedirs(os.path.dirname(history_path), exist_ok=True)
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}")
except Exception as 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."""
history = load_history()
history.append({
entry = {
"platform": platform,
"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)
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():
"""Vide l'historique."""
history_path = getattr(config, 'HISTORY_PATH', DEFAULT_HISTORY_PATH)
try:
with open(history_path, "w") as f:
with open(history_path, "w", encoding='utf-8') as f:
json.dump([], f)
logger.info(f"Historique vidé : {history_path}")
except Exception as e:

View File

@@ -8,8 +8,10 @@ import asyncio
import config
from config import OTA_VERSION_ENDPOINT, OTA_UPDATE_SCRIPT
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 queue
import time
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]
# 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():
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:
dest_dir = None
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")
break
if not dest_dir:
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
dest_dir = os.path.join("/userdata/roms", platform)
dest_dir = os.path.join("/userdata/roms", platform.lower().replace(" ", ""))
logger.debug(f"Vérification répertoire destination: {dest_dir}")
os.makedirs(dest_dir, exist_ok=True)
if not os.access(dest_dir, os.W_OK):
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}")
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'}
logger.debug(f"Envoi requête GET à {url}")
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()
total_size = int(response.headers.get('content-length', 0))
logger.debug(f"Taille totale: {total_size} octets")
with lock:
config.download_progress[url]["total_size"] = total_size
config.needs_redraw = True # Forcer le redraw
# Initialiser la progression avec task_id
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
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:
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:
size_received = len(chunk)
f.write(chunk)
downloaded += len(chunk)
with lock:
config.download_progress[url]["downloaded_size"] = downloaded
config.download_progress[url]["status"] = "Téléchargement"
config.download_progress[url]["progress_percent"] = (downloaded / total_size * 100) if total_size > 0 else 0
config.needs_redraw = True # Forcer le redraw
#logger.debug(f"Progression: {downloaded}/{total_size} octets, {config.download_progress[url]['progress_percent']:.1f}%")
downloaded += size_received
current_time = time.time()
if current_time - last_update_time >= update_interval:
progress = (downloaded / total_size * 100) if total_size > 0 else 0
progress_queue.put((task_id, downloaded, total_size))
# logger.debug(f"Progress update sent: {progress:.1f}% for {game_name}, task_id={task_id}")
last_update_time = current_time
else:
logger.debug("Chunk vide reçu")
if is_zip_non_supported:
with lock:
config.download_progress[url]["downloaded_size"] = 0
config.download_progress[url]["total_size"] = 0
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}"
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:
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[1] = f"Erreur téléchargement {game_name}"
result[1] = f"Erreur téléchargement {game_name}: {str(e)}"
finally:
logger.debug(f"Thread téléchargement terminé pour {url}")
with lock:
config.download_result_message = result[1]
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'}")
logger.debug(f"Thread téléchargement 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}")
thread = threading.Thread(target=download_thread)
logger.debug(f"Démarrage thread pour {url}")
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]
def is_1fichier_url(url):
"""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}")
async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False, task_id=None):
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}")
result = [None, None]
def download_thread():
logger.debug(f"Thread téléchargement 1fichier démarré pour {url}")
# Vider la file d'attente avant de commencer
while not progress_queue.empty():
try:
# Nettoyer l'URL
link = url.split('&af=')[0]
progress_queue.get_nowait()
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
for platform_dict in config.platform_dicts:
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):
raise PermissionError(f"Pas de permission d'écriture dans {dest_dir}")
# Préparer les en-têtes et le payload
headers = {
"Authorization": f"Bearer {config.API_KEY_1FICHIER}",
"Content-Type": "application/json"
@@ -280,7 +280,6 @@ def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False)
"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}")
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}")
@@ -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)
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}")
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}")
@@ -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"
return
# Étape 3 : Initialiser la progression
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
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):
try:
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))
logger.debug(f"Taille totale: {total_size} octets")
with lock:
config.download_progress[url]["total_size"] = total_size
config.needs_redraw = True
for entry in config.history:
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
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:
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:
f.write(chunk)
downloaded += len(chunk)
with lock:
config.download_progress[url]["downloaded_size"] = downloaded
config.download_progress[url]["status"] = "Téléchargement"
config.download_progress[url]["progress_percent"] = (downloaded / total_size * 100) if total_size > 0 else 0
config.needs_redraw = True
#logger.debug(f"Progression: {downloaded}/{total_size} octets, {config.download_progress[url]['progress_percent']:.1f}%")
current_time = time.time()
if current_time - last_update_time >= update_interval:
with lock:
for entry in config.history:
if entry["url"] == url and entry["status"] == "downloading":
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:
with lock:
config.download_progress[url]["downloaded_size"] = 0
config.download_progress[url]["total_size"] = 0
config.download_progress[url]["status"] = "Extracting"
config.download_progress[url]["progress_percent"] = 0
config.needs_redraw = True
for entry in config.history:
if entry["url"] == url and entry["status"] == "Téléchargement":
entry["progress"] = 0
entry["status"] = "Extracting"
config.needs_redraw = True
break
extension = os.path.splitext(dest_path)[1].lower()
if extension == ".zip":
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:
logger.error(f"Tentative {attempt + 1} échouée : {e}")
if attempt < retries - 1:
import time
time.sleep(retry_delay)
else:
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:
logger.error(f"Erreur API 1fichier : {e}")
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:
logger.debug(f"Thread téléchargement 1fichier terminé pour {url}")
with lock:
config.download_result_message = result[1]
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'}")
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}")
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.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 threading
import zipfile
import time
import random
from config import JSON_EXTENSIONS
from history import save_history
from datetime import datetime
@@ -177,11 +179,12 @@ def write_unavailable_systems():
except Exception as e:
logger.error(f"Erreur lors de l'écriture du fichier {log_file} : {str(e)}")
def truncate_text_middle(text, font, max_width):
"""Tronque le texte en insérant '...' au milieu, en préservant le début et la fin, sans extension de fichier."""
# Supprimer l'extension de fichier
text = text.rsplit('.', 1)[0] if '.' in text else text
def truncate_text_middle(text, font, max_width, is_filename=True):
"""Tronque le texte en insérant '...' au milieu, en préservant le début et la fin.
Si is_filename=False, ne supprime pas l'extension."""
# Supprimer l'extension uniquement si is_filename est True
if is_filename:
text = text.rsplit('.', 1)[0] if '.' in text else text
text_width = font.size(text)[0]
if text_width <= max_width:
return text
@@ -191,7 +194,7 @@ def truncate_text_middle(text, font, max_width):
if max_text_width <= 0:
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)
left = []
right = []
@@ -200,14 +203,9 @@ def truncate_text_middle(text, font, max_width):
left_idx = 0
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:
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
# Ajouter à droite en priorité
if left_idx <= right_idx:
right.insert(0, chars[right_idx])
right_width = font.size(''.join(right))[0]
@@ -215,6 +213,14 @@ def truncate_text_middle(text, font, max_width):
right.pop(0)
break
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
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
# Fonction pour extraire le contenu d'un fichier ZIP
def extract_zip(zip_path, dest_dir, url):
"""Extrait le contenu du fichier ZIP dans le dossier cible avec un suivi progressif de la progression."""
logger.debug(f"Extraction de {zip_path} dans {dest_dir}")
try:
lock = threading.Lock()
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())
logger.info(f"Taille totale à extraire: {total_size} octets")
if total_size == 0:
@@ -319,7 +326,9 @@ def extract_zip(zip_path, dest_dir, url):
extracted_size = 0
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():
if info.is_dir():
continue
@@ -335,15 +344,19 @@ def extract_zip(zip_path, dest_dir, url):
dest.write(chunk)
file_extracted += len(chunk)
extracted_size += len(chunk)
current_time = time.time()
with lock:
config.download_progress[url]["downloaded_size"] = extracted_size
config.download_progress[url]["total_size"] = total_size
config.download_progress[url]["status"] = "Extracting"
config.download_progress[url]["progress_percent"] = (extracted_size / total_size * 100) if total_size > 0 else 0
config.needs_redraw = True # Forcer le redraw
# Logger une seule ligne à la fin de l'extraction du fichier
progress_percentage = (extracted_size / total_size * 100) if total_size > 0 else 0
logger.debug(f"Extraction terminée pour {info.filename}, file_extracted: {file_extracted}/{file_size}, total_extracted: {extracted_size}/{total_size}, progression: {progress_percentage:.1f}%")
for entry in config.history:
if entry["url"] == url and entry["status"] in ["Téléchargement", "Extracting"]:
entry["status"] = "Extracting"
entry["progress"] = (extracted_size / total_size * 100) if total_size > 0 else 0
entry["message"] = "Extraction en cours"
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={entry['progress']:.1f}%")
config.needs_redraw = True
break
os.chmod(file_path, 0o644)
for root, dirs, _ in os.walk(dest_dir):
@@ -352,10 +365,17 @@ def extract_zip(zip_path, dest_dir, url):
os.remove(zip_path)
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:
logger.error(f"Erreur lors de l'extraction de {zip_path}: {e}")
return False, str(e)
logger.error(f"Erreur lors de l'extraction de {zip_path}: {str(e)}")
return False, f"Échec de l'extraction: {str(e)}"
# Fonction pour extraire le contenu d'un fichier RAR
def extract_rar(rar_path, dest_dir, url):