mirror of
https://github.com/RetroGameSets/RGSX.git
synced 2026-03-19 16:26:00 +01:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9a610b5dd | ||
|
|
bd3b885736 |
@@ -28,7 +28,7 @@ from display import (
|
||||
draw_toast, show_toast, THEME_COLORS
|
||||
)
|
||||
from language import _
|
||||
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates, cancel_all_downloads
|
||||
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates, cancel_all_downloads, download_queue_worker
|
||||
from controls import handle_controls, validate_menu_state, process_key_repeats, get_emergency_controls
|
||||
from controls_mapper import map_controls, draw_controls_mapping, get_actions
|
||||
from controls import load_controls_config
|
||||
@@ -213,7 +213,7 @@ except Exception:
|
||||
normalized_names = [n.lower() for n in joystick_names]
|
||||
if not joystick_names:
|
||||
joystick_names = ["Clavier"]
|
||||
print("Aucun joystick détecté, utilisation du clavier par défaut")
|
||||
print("Aucun joystick detecte, utilisation du clavier par defaut")
|
||||
logger.debug("Aucun joystick détecté, utilisation du clavier par défaut.")
|
||||
config.joystick = False
|
||||
config.keyboard = True
|
||||
@@ -438,6 +438,11 @@ async def main():
|
||||
# Démarrer le serveur web en arrière-plan
|
||||
start_web_server()
|
||||
|
||||
# Démarrer le worker de la queue de téléchargement
|
||||
queue_worker_thread = threading.Thread(target=download_queue_worker, daemon=True)
|
||||
queue_worker_thread.start()
|
||||
logger.info("Worker de la queue de téléchargement démarré")
|
||||
|
||||
running = True
|
||||
loading_step = "none"
|
||||
sources = []
|
||||
@@ -473,7 +478,7 @@ async def main():
|
||||
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 config.menu_state == "history" and any(entry["status"] in ["Downloading", "Téléchargement"] for entry in config.history):
|
||||
if current_time - last_redraw_time >= 100:
|
||||
config.needs_redraw = True
|
||||
last_redraw_time = current_time
|
||||
|
||||
@@ -13,7 +13,7 @@ except Exception:
|
||||
pygame = None # type: ignore
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "2.3.3.1"
|
||||
app_version = "2.3.3.3"
|
||||
|
||||
|
||||
def get_application_root():
|
||||
@@ -133,6 +133,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# File d'attente de téléchargements (jobs en attente)
|
||||
download_queue = [] # Liste de dicts: {url, platform, game_name, ...}
|
||||
pending_download_is_queue = False # Indique si pending_download doit être ajouté à la queue
|
||||
# Indique si un téléchargement est en cours
|
||||
download_active = False
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ def load_controls_config(path=CONTROLS_CONFIG_PATH):
|
||||
dev_field = preset.get('device') if isinstance(preset, dict) else None
|
||||
if isinstance(dev_field, str) and _sanitize(dev_field) == target_norm:
|
||||
logging.getLogger(__name__).info(f"Chargement préréglage (device) depuis le fichier: {fname}")
|
||||
print(f"Chargement préréglage (device) depuis le fichier: {fname}")
|
||||
print(f"Chargement prereglage (device) depuis le fichier: {fname}")
|
||||
return preset
|
||||
except Exception as e:
|
||||
logging.getLogger(__name__).warning(f"Échec scan préréglages par device: {e}")
|
||||
@@ -559,7 +559,11 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "confirm"):
|
||||
config.search_query += keyboard_layout[row][col]
|
||||
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
|
||||
# Appliquer d'abord les filtres avancés si actifs, puis le filtre par nom
|
||||
base_games = config.games
|
||||
if config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
base_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filtered_games = [game for game in base_games if config.search_query.lower() in game[0].lower()]
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -567,14 +571,22 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif is_input_matched(event, "delete"):
|
||||
if config.search_query:
|
||||
config.search_query = config.search_query[:-1]
|
||||
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
|
||||
# Appliquer d'abord les filtres avancés si actifs, puis le filtre par nom
|
||||
base_games = config.games
|
||||
if config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
base_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filtered_games = [game for game in base_games if config.search_query.lower() in game[0].lower()]
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
#logger.debug(f"Suppression caractère: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
|
||||
elif is_input_matched(event, "space"):
|
||||
config.search_query += " "
|
||||
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
|
||||
# Appliquer d'abord les filtres avancés si actifs, puis le filtre par nom
|
||||
base_games = config.games
|
||||
if config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
base_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filtered_games = [game for game in base_games if config.search_query.lower() in game[0].lower()]
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -583,7 +595,13 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.search_mode = False
|
||||
config.search_query = ""
|
||||
config.selected_key = (0, 0)
|
||||
config.filtered_games = config.games
|
||||
# Restaurer les jeux filtrés par les filtres avancés si actifs
|
||||
if hasattr(config, 'game_filter_obj') and config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
config.filtered_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filter_active = True
|
||||
else:
|
||||
config.filtered_games = config.games
|
||||
config.filter_active = False
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -605,8 +623,13 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif is_input_matched(event, "cancel"):
|
||||
config.search_mode = False
|
||||
config.search_query = ""
|
||||
config.filtered_games = config.games
|
||||
config.filter_active = False
|
||||
# Restaurer les jeux filtrés par les filtres avancés si actifs
|
||||
if hasattr(config, 'game_filter_obj') and config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
config.filtered_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filter_active = True
|
||||
else:
|
||||
config.filtered_games = config.games
|
||||
config.filter_active = False
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -615,7 +638,11 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Saisie de texte alphanumérique
|
||||
if event.unicode.isalnum() or event.unicode == ' ':
|
||||
config.search_query += event.unicode
|
||||
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
|
||||
# Appliquer d'abord les filtres avancés si actifs, puis le filtre par nom
|
||||
base_games = config.games
|
||||
if config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
base_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filtered_games = [game for game in base_games if config.search_query.lower() in game[0].lower()]
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -624,7 +651,11 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif is_input_matched(event, "delete"):
|
||||
if config.search_query:
|
||||
config.search_query = config.search_query[:-1]
|
||||
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
|
||||
# Appliquer d'abord les filtres avancés si actifs, puis le filtre par nom
|
||||
base_games = config.games
|
||||
if config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
base_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filtered_games = [game for game in base_games if config.search_query.lower() in game[0].lower()]
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -709,6 +740,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Si extension non supportée ET pas en archive connu, afficher avertissement
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
config.pending_download = pending_download
|
||||
config.pending_download_is_queue = True # Marquer comme action queue
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
@@ -782,29 +814,69 @@ def handle_controls(event, sources, joystick, screen):
|
||||
if config.extension_confirm_selection == 0: # 0 = Oui, 1 = Non
|
||||
if config.pending_download and len(config.pending_download) == 4:
|
||||
url, platform, game_name, is_zip_non_supported = config.pending_download
|
||||
if is_1fichier_url(url):
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
if missing_all_provider_keys():
|
||||
logger.warning("Aucune clé API - Mode gratuit 1fichier sera utilisé (attente requise)")
|
||||
|
||||
|
||||
# Vérifier si c'est une action queue
|
||||
is_queue_action = getattr(config, 'pending_download_is_queue', False)
|
||||
|
||||
if is_queue_action:
|
||||
# Ajouter à la queue au lieu de télécharger immédiatement
|
||||
task_id = str(pygame.time.get_ticks())
|
||||
task = asyncio.create_task(download_from_1fichier(url, platform, game_name, is_zip_non_supported, task_id))
|
||||
queue_item = {
|
||||
'url': url,
|
||||
'platform': platform,
|
||||
'game_name': game_name,
|
||||
'is_zip_non_supported': is_zip_non_supported,
|
||||
'is_1fichier': is_1fichier_url(url),
|
||||
'task_id': task_id,
|
||||
'status': 'Queued'
|
||||
}
|
||||
config.download_queue.append(queue_item)
|
||||
|
||||
# Ajouter une entrée à l'historique avec status "Queued"
|
||||
config.history.append({
|
||||
'platform': platform,
|
||||
'game_name': game_name,
|
||||
'status': 'Queued',
|
||||
'url': url,
|
||||
'progress': 0,
|
||||
'message': _("download_queued"),
|
||||
'timestamp': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'downloaded_size': 0,
|
||||
'total_size': 0,
|
||||
'task_id': task_id
|
||||
})
|
||||
save_history(config.history)
|
||||
|
||||
# Afficher un toast de notification
|
||||
show_toast(f"{game_name}\n{_('download_queued')}")
|
||||
|
||||
# Le worker de la queue détectera automatiquement le nouvel élément
|
||||
logger.debug(f"{game_name} ajouté à la file d'attente après confirmation. Queue size: {len(config.download_queue)}")
|
||||
else:
|
||||
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)
|
||||
# Afficher un toast de notification
|
||||
show_toast(f"{_('download_started')}: {game_name}")
|
||||
# Téléchargement immédiat
|
||||
if is_1fichier_url(url):
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
if missing_all_provider_keys():
|
||||
logger.warning("Aucune clé API - Mode gratuit 1fichier sera utilisé (attente requise)")
|
||||
|
||||
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_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)
|
||||
# Afficher un toast de notification
|
||||
show_toast(f"{_('download_started')}: {game_name}")
|
||||
logger.debug(f"[CONTROLS_EXT_WARNING] Téléchargement confirmé après avertissement: {game_name} pour {platform} depuis {url}, task_id={task_id}")
|
||||
|
||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"[CONTROLS_EXT_WARNING] Téléchargement confirmé après avertissement: {game_name} pour {platform} depuis {url}, task_id={task_id}")
|
||||
config.pending_download = None
|
||||
config.pending_download_is_queue = False
|
||||
config.extension_confirm_selection = 0 # Réinitialiser la sélection
|
||||
action = "download"
|
||||
# Téléchargement simple - retourner au menu précédent
|
||||
# Retourner au menu précédent
|
||||
config.menu_state = config.previous_menu_state if config.previous_menu_state else "game"
|
||||
logger.debug(f"[CONTROLS_EXT_WARNING] Retour au menu {config.menu_state} après confirmation")
|
||||
else:
|
||||
@@ -912,22 +984,35 @@ def handle_controls(event, sources, joystick, screen):
|
||||
if is_input_matched(event, "confirm"):
|
||||
if config.confirm_cancel_selection == 1: # Oui
|
||||
entry = config.history[config.current_history_item]
|
||||
task_id = entry.get("task_id")
|
||||
url = entry.get("url")
|
||||
# Annuler la tâche correspondante
|
||||
for task_id, (task, task_url, game_name, platform) in list(config.download_tasks.items()):
|
||||
if task_url == url:
|
||||
game_name = entry.get("game_name", "Unknown")
|
||||
|
||||
# Annuler via cancel_events (pour les threads de téléchargement)
|
||||
try:
|
||||
request_cancel(task_id)
|
||||
logger.debug(f"Signal d'annulation envoyé pour task_id={task_id}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Erreur lors de l'envoi du signal d'annulation: {e}")
|
||||
|
||||
# Annuler aussi la tâche asyncio si elle existe (pour les téléchargements directs)
|
||||
for tid, (task, task_url, tname, tplatform) in list(config.download_tasks.items()):
|
||||
if tid == task_id or task_url == url:
|
||||
try:
|
||||
request_cancel(task_id)
|
||||
except Exception:
|
||||
pass
|
||||
task.cancel()
|
||||
del config.download_tasks[task_id]
|
||||
entry["status"] = "Canceled"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = _("download_canceled") if _ else "Download canceled"
|
||||
save_history(config.history)
|
||||
logger.debug(f"Téléchargement annulé: {game_name}")
|
||||
task.cancel()
|
||||
del config.download_tasks[tid]
|
||||
logger.debug(f"Tâche asyncio annulée: {tname}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Erreur lors de l'annulation de la tâche asyncio: {e}")
|
||||
break
|
||||
|
||||
# Mettre à jour l'entrée historique
|
||||
entry["status"] = "Canceled"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = _("download_canceled") if _ else "Download canceled"
|
||||
save_history(config.history)
|
||||
logger.debug(f"Téléchargement annulé: {game_name}")
|
||||
|
||||
config.menu_state = "history"
|
||||
config.needs_redraw = True
|
||||
else: # Non
|
||||
@@ -1003,7 +1088,13 @@ def handle_controls(event, sources, joystick, screen):
|
||||
options.append("scraper")
|
||||
|
||||
# Options selon statut
|
||||
if status == "Download_OK" or status == "Completed":
|
||||
if status == "Queued":
|
||||
# En attente dans la queue
|
||||
options.append("remove_from_queue")
|
||||
elif status in ["Downloading", "Téléchargement", "Extracting"]:
|
||||
# Téléchargement en cours
|
||||
options.append("cancel_download")
|
||||
elif status == "Download_OK" or status == "Completed":
|
||||
# Vérifier si c'est une archive ET si le fichier existe
|
||||
if actual_filename and file_exists:
|
||||
ext = os.path.splitext(actual_filename)[1].lower()
|
||||
@@ -1046,7 +1137,37 @@ def handle_controls(event, sources, joystick, screen):
|
||||
selected_option = options[sel]
|
||||
logger.debug(f"history_game_options: CONFIRM option={selected_option}")
|
||||
|
||||
if selected_option == "download_folder":
|
||||
if selected_option == "remove_from_queue":
|
||||
# Retirer de la queue
|
||||
task_id = entry.get("task_id")
|
||||
url = entry.get("url")
|
||||
|
||||
# Chercher et retirer de la queue
|
||||
for i, queue_item in enumerate(config.download_queue):
|
||||
if queue_item.get("task_id") == task_id or queue_item.get("url") == url:
|
||||
config.download_queue.pop(i)
|
||||
logger.debug(f"Jeu retiré de la queue: {game_name}")
|
||||
break
|
||||
|
||||
# Mettre à jour l'entrée historique avec status Canceled
|
||||
entry["status"] = "Canceled"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = _("download_canceled") if _ else "Download canceled"
|
||||
save_history(config.history)
|
||||
|
||||
# Retour à l'historique
|
||||
config.menu_state = "history"
|
||||
config.needs_redraw = True
|
||||
|
||||
elif selected_option == "cancel_download":
|
||||
# Rediriger vers le dialogue de confirmation (même que bouton cancel)
|
||||
config.previous_menu_state = "history"
|
||||
config.menu_state = "confirm_cancel_download"
|
||||
config.confirm_cancel_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug("Redirection vers confirm_cancel_download depuis history_game_options")
|
||||
|
||||
elif selected_option == "download_folder":
|
||||
# Afficher le chemin de destination
|
||||
config.previous_menu_state = "history_game_options"
|
||||
config.menu_state = "history_show_folder"
|
||||
@@ -2140,7 +2261,11 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Recherche par nom (mode existant)
|
||||
config.search_mode = True
|
||||
config.search_query = ""
|
||||
config.filtered_games = config.games
|
||||
# Initialiser avec les jeux déjà filtrés par les filtres avancés si actifs
|
||||
if hasattr(config, 'game_filter_obj') and config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
config.filtered_games = config.game_filter_obj.apply_filters(config.games)
|
||||
else:
|
||||
config.filtered_games = config.games
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.selected_key = (0, 0)
|
||||
|
||||
@@ -1042,14 +1042,14 @@ def draw_game_list(screen):
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||||
screen.blit(title_surface, title_rect)
|
||||
elif config.filter_active:
|
||||
# Display filter active indicator with count
|
||||
if hasattr(config, 'game_filter_obj') and config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
total_games = len(config.games)
|
||||
filtered_count = len(games)
|
||||
filter_text = _("filter_games_shown").format(filtered_count, total_games)
|
||||
else:
|
||||
filter_text = _("game_filter").format(config.search_query)
|
||||
title_surface = config.font.render(filter_text, True, THEME_COLORS["green"])
|
||||
# Afficher le nom de la plateforme avec indicateur de filtre actif
|
||||
filter_indicator = " (Active Filter)"
|
||||
if config.search_query:
|
||||
# Si recherche par nom active, afficher aussi la recherche
|
||||
filter_indicator = f" - {_('game_filter').format(config.search_query)}"
|
||||
|
||||
title_text = _("game_count").format(platform_name, game_count) + filter_indicator
|
||||
title_surface = config.title_font.render(title_text, True, THEME_COLORS["green"])
|
||||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20))
|
||||
title_rect_inflated = title_rect.inflate(60, 30)
|
||||
title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10)
|
||||
@@ -1057,7 +1057,12 @@ def draw_game_list(screen):
|
||||
pygame.draw.rect(screen, THEME_COLORS["border_selected"], title_rect_inflated, 3, border_radius=12)
|
||||
screen.blit(title_surface, title_rect)
|
||||
else:
|
||||
title_text = _("game_count").format(platform_name, game_count)
|
||||
# Ajouter indicateur de filtre actif si filtres avancés sont actifs
|
||||
filter_indicator = ""
|
||||
if hasattr(config, 'game_filter_obj') and config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
filter_indicator = " (Active Filter)"
|
||||
|
||||
title_text = _("game_count").format(platform_name, game_count) + filter_indicator
|
||||
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
|
||||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20))
|
||||
title_rect_inflated = title_rect.inflate(60, 30)
|
||||
@@ -3158,7 +3163,15 @@ def draw_history_game_options(screen):
|
||||
option_labels.append(_("history_option_scraper"))
|
||||
|
||||
# Options selon statut
|
||||
if status == "Download_OK" or status == "Completed":
|
||||
if status == "Queued":
|
||||
# En attente dans la queue
|
||||
options.append("remove_from_queue")
|
||||
option_labels.append(_("history_option_remove_from_queue"))
|
||||
elif status in ["Downloading", "Téléchargement", "Extracting"]:
|
||||
# Téléchargement en cours
|
||||
options.append("cancel_download")
|
||||
option_labels.append(_("history_option_cancel_download"))
|
||||
elif status == "Download_OK" or status == "Completed":
|
||||
# Vérifier si c'est une archive ET si le fichier existe
|
||||
if actual_filename and file_exists:
|
||||
ext = os.path.splitext(actual_filename)[1].lower()
|
||||
|
||||
@@ -69,30 +69,67 @@ class GameFilters:
|
||||
name = game_name.upper()
|
||||
regions = []
|
||||
|
||||
# Patterns de région communs
|
||||
if 'USA' in name or 'US)' in name:
|
||||
regions.append('USA')
|
||||
# Patterns de région communs - chercher les codes entre parenthèses d'abord
|
||||
# Codes de région/langue dans les parenthèses (Ex: (Fr,De) ou (En,Nl))
|
||||
paren_content = re.findall(r'\(([^)]+)\)', name)
|
||||
for content in paren_content:
|
||||
# Codes de langue/région séparés par virgules
|
||||
codes = [c.strip() for c in content.split(',')]
|
||||
for code in codes:
|
||||
if code in ['FR', 'FRA']:
|
||||
if 'France' not in regions:
|
||||
regions.append('France')
|
||||
elif code in ['DE', 'GER', 'DEU']:
|
||||
if 'Germany' not in regions:
|
||||
regions.append('Germany')
|
||||
elif code in ['EN', 'ENG'] or code.startswith('EN-'):
|
||||
# EN peut être USA, Europe ou autre - on vérifie le contexte
|
||||
if 'EU' in codes or 'EUR' in codes:
|
||||
if 'Europe' not in regions:
|
||||
regions.append('Europe')
|
||||
elif code in ['ES', 'ESP', 'SPA']:
|
||||
if 'Other' not in regions:
|
||||
regions.append('Other')
|
||||
elif code in ['IT', 'ITA']:
|
||||
if 'Other' not in regions:
|
||||
regions.append('Other')
|
||||
elif code in ['NL', 'NLD', 'DU', 'DUT']:
|
||||
if 'Europe' not in regions:
|
||||
regions.append('Europe')
|
||||
elif code in ['PT', 'POR']:
|
||||
if 'Other' not in regions:
|
||||
regions.append('Other')
|
||||
|
||||
# Patterns de région complets (mots entiers)
|
||||
if 'USA' in name or 'US)' in name or re.search(r'\bUS\b', name):
|
||||
if 'USA' not in regions:
|
||||
regions.append('USA')
|
||||
if 'CANADA' in name or 'CA)' in name:
|
||||
regions.append('Canada')
|
||||
if 'EUROPE' in name or 'EU)' in name:
|
||||
regions.append('Europe')
|
||||
if 'Canada' not in regions:
|
||||
regions.append('Canada')
|
||||
if 'EUROPE' in name or 'EU)' in name or re.search(r'\bEU\b', name):
|
||||
if 'Europe' not in regions:
|
||||
regions.append('Europe')
|
||||
if 'FRANCE' in name or 'FR)' in name:
|
||||
regions.append('France')
|
||||
if 'France' not in regions:
|
||||
regions.append('France')
|
||||
if 'GERMANY' in name or 'DE)' in name or 'GER)' in name:
|
||||
regions.append('Germany')
|
||||
if 'JAPAN' in name or 'JP)' in name or 'JPN)' in name:
|
||||
regions.append('Japan')
|
||||
if 'Germany' not in regions:
|
||||
regions.append('Germany')
|
||||
if 'JAPAN' in name or 'JP)' in name or 'JPN)' in name or re.search(r'\bJP\b', name):
|
||||
if 'Japan' not in regions:
|
||||
regions.append('Japan')
|
||||
if 'KOREA' in name or 'KR)' in name or 'KOR)' in name:
|
||||
regions.append('Korea')
|
||||
if 'Korea' not in regions:
|
||||
regions.append('Korea')
|
||||
if 'WORLD' in name:
|
||||
regions.append('World')
|
||||
if 'World' not in regions:
|
||||
regions.append('World')
|
||||
|
||||
# Autres régions
|
||||
if re.search(r'\b(AUSTRALIA|ASIA|KOREA|BRAZIL|CHINA|RUSSIA|SCANDINAVIA|'
|
||||
r'SPAIN|FRANCE|GERMANY|ITALY|CANADA)\b', name):
|
||||
if 'CANADA' in name:
|
||||
regions.append('Canada')
|
||||
else:
|
||||
if re.search(r'\b(AUSTRALIA|ASIA|BRAZIL|CHINA|RUSSIA|SCANDINAVIA|'
|
||||
r'SPAIN|ITALY)\b', name):
|
||||
if 'Other' not in regions:
|
||||
regions.append('Other')
|
||||
|
||||
# Si aucune région trouvée
|
||||
@@ -157,14 +194,22 @@ class GameFilters:
|
||||
|
||||
def get_region_priority(self, game_name: str) -> int:
|
||||
"""Obtient la priorité de région pour un jeu (pour one-rom-per-game)"""
|
||||
name = game_name.upper()
|
||||
# Utiliser la fonction de détection de régions pour être cohérent
|
||||
game_regions = self.get_game_regions(game_name)
|
||||
|
||||
for i, region in enumerate(self.region_priority):
|
||||
region_upper = region.upper()
|
||||
if region_upper in name:
|
||||
return i
|
||||
# Trouver la meilleure priorité parmi toutes les régions détectées
|
||||
best_priority = len(self.region_priority) # Par défaut: priorité la plus basse
|
||||
|
||||
return len(self.region_priority) # Autres régions (priorité la plus basse)
|
||||
for region in game_regions:
|
||||
try:
|
||||
priority = self.region_priority.index(region)
|
||||
if priority < best_priority:
|
||||
best_priority = priority
|
||||
except ValueError:
|
||||
# La région n'est pas dans la liste de priorité
|
||||
continue
|
||||
|
||||
return best_priority
|
||||
|
||||
def apply_filters(self, games: List[Tuple]) -> List[Tuple]:
|
||||
"""
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"free_mode_completed": "[Kostenloser Modus] Abgeschlossen: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download vom Benutzer abgebrochen.",
|
||||
"download_removed_from_queue": "Aus der Download-Warteschlange entfernt",
|
||||
"extension_warning_zip": "Die Datei '{0}' ist ein Archiv und Batocera unterstützt keine Archive für dieses System. Die automatische Extraktion der Datei erfolgt nach dem Download, fortfahren?",
|
||||
"extension_warning_unsupported": "Die Dateierweiterung für '{0}' wird laut der Konfiguration es_systems.cfg von Batocera nicht unterstützt. Möchtest du fortfahren?",
|
||||
"extension_warning_enable_unknown_hint": "\nUm diese Meldung auszublenden: \"Warnung bei unbekannter Erweiterung ausblenden\" in Pausenmenü > Anzeige aktivieren",
|
||||
@@ -247,6 +248,8 @@
|
||||
"history_option_extract_archive": "Archiv extrahieren",
|
||||
"history_option_open_file": "Datei öffnen",
|
||||
"history_option_scraper": "Metadaten abrufen",
|
||||
"history_option_remove_from_queue": "Aus Warteschlange entfernen",
|
||||
"history_option_cancel_download": "Download abbrechen",
|
||||
"history_option_delete_game": "Spiel löschen",
|
||||
"history_option_error_info": "Fehlerdetails",
|
||||
"history_option_retry": "Download wiederholen",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"free_mode_completed": "[Free mode] Completed: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download canceled by user.",
|
||||
"download_removed_from_queue": "Removed from download queue",
|
||||
"extension_warning_zip": "The file '{0}' is an archive and Batocera does not support archives for this system. Automatic extraction will occur after download, continue?",
|
||||
"extension_warning_unsupported": "The file extension for '{0}' is not supported by Batocera according to the es_systems.cfg configuration. Do you want to continue?",
|
||||
"extension_warning_enable_unknown_hint": "\nTo hide this message: enable \"Hide unknown extension warning\" in Pause Menu > Display",
|
||||
@@ -249,6 +250,8 @@
|
||||
"history_option_extract_archive": "Extract archive",
|
||||
"history_option_open_file": "Open file",
|
||||
"history_option_scraper": "Scrape metadata",
|
||||
"history_option_remove_from_queue": "Remove from queue",
|
||||
"history_option_cancel_download": "Cancel download",
|
||||
"history_option_delete_game": "Delete game",
|
||||
"history_option_error_info": "Error details",
|
||||
"history_option_retry": "Retry download",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"free_mode_completed": "[Modo gratuito] Completado: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Descarga cancelada por el usuario.",
|
||||
"download_removed_from_queue": "Eliminado de la cola de descarga",
|
||||
"extension_warning_zip": "El archivo '{0}' es un archivo comprimido y Batocera no soporta archivos comprimidos para este sistema. La extracción automática del archivo se realizará después de la descarga, ¿continuar?",
|
||||
"extension_warning_unsupported": "La extensión del archivo '{0}' no está soportada por Batocera según la configuración es_systems.cfg. ¿Deseas continuar?",
|
||||
"extension_warning_enable_unknown_hint": "\nPara no mostrar este mensaje: activa \"Ocultar aviso de extensión desconocida\" en Menú de pausa > Pantalla",
|
||||
@@ -249,6 +250,8 @@
|
||||
"history_option_extract_archive": "Extraer archivo",
|
||||
"history_option_open_file": "Abrir archivo",
|
||||
"history_option_scraper": "Obtener metadatos",
|
||||
"history_option_remove_from_queue": "Quitar de la cola",
|
||||
"history_option_cancel_download": "Cancelar descarga",
|
||||
"history_option_delete_game": "Eliminar juego",
|
||||
"history_option_error_info": "Detalles del error",
|
||||
"history_option_retry": "Reintentar descarga",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"free_mode_completed": "[Mode gratuit] Terminé: {0}",
|
||||
"download_status": "{0} : {1}",
|
||||
"download_canceled": "Téléchargement annulé par l'utilisateur.",
|
||||
"download_removed_from_queue": "Retiré de la file de téléchargement",
|
||||
"extension_warning_zip": "Le fichier '{0}' est une archive et Batocera ne prend pas en charge les archives pour ce système. L'extraction automatique du fichier aura lieu après le téléchargement, continuer ?",
|
||||
"extension_warning_unsupported": "L'extension du fichier '{0}' n'est pas supportée par Batocera d'après la configuration es_systems.cfg. Voulez-vous continuer ?",
|
||||
"extension_warning_enable_unknown_hint": "\nPour ne plus afficher ce messager : Activer l'option \"Masquer avertissement\" dans le Menu Pause>Display",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"free_mode_completed": "[Modalità gratuita] Completato: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download annullato dall'utente.",
|
||||
"download_removed_from_queue": "Rimosso dalla coda di download",
|
||||
"extension_warning_zip": "Il file '{0}' è un archivio e Batocera non supporta archivi per questo sistema. L'estrazione automatica avverrà dopo il download, continuare?",
|
||||
"extension_warning_unsupported": "L'estensione del file '{0}' non è supportata da Batocera secondo la configurazione di es_systems.cfg. Vuoi continuare?",
|
||||
"extension_warning_enable_unknown_hint": "\nPer non visualizzare questo messaggio: abilita \"Nascondi avviso estensione sconosciuta\" in Menu Pausa > Schermo",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"free_mode_completed": "[Modo gratuito] Concluído: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download cancelado pelo usuário.",
|
||||
"download_removed_from_queue": "Removido da fila de download",
|
||||
"extension_warning_zip": "O arquivo '{0}' é um arquivo compactado e o Batocera não suporta arquivos compactados para este sistema. A extração automática ocorrerá após o download, continuar?",
|
||||
"extension_warning_unsupported": "A extensão do arquivo '{0}' não é suportada pelo Batocera segundo a configuração es_systems.cfg. Deseja continuar?",
|
||||
"extension_warning_enable_unknown_hint": "\nPara não ver esta mensagem: ative \"Ocultar aviso de extensão desconhecida\" em Menu de Pausa > Exibição",
|
||||
@@ -248,6 +249,8 @@
|
||||
"history_option_extract_archive": "Extrair arquivo",
|
||||
"history_option_open_file": "Abrir arquivo",
|
||||
"history_option_scraper": "Obter metadados",
|
||||
"history_option_remove_from_queue": "Remover da fila",
|
||||
"history_option_cancel_download": "Cancelar download",
|
||||
"history_option_delete_game": "Excluir jogo",
|
||||
"history_option_error_info": "Detalhes do erro",
|
||||
"history_option_retry": "Tentar novamente",
|
||||
|
||||
@@ -925,6 +925,16 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
logger.info(f"Le fichier {dest_path} existe déjà et la taille est correcte, téléchargement ignoré")
|
||||
result[0] = True
|
||||
result[1] = _("network_download_ok").format(game_name) + _("download_already_present")
|
||||
|
||||
# Mettre à jour l'historique
|
||||
for entry in config.history:
|
||||
if entry.get("url") == url:
|
||||
entry["status"] = "Download_OK"
|
||||
entry["progress"] = 100
|
||||
entry["message"] = result[1]
|
||||
save_history(config.history)
|
||||
break
|
||||
|
||||
# Afficher un toast au lieu d'ouvrir l'historique
|
||||
try:
|
||||
show_toast(result[1])
|
||||
@@ -933,6 +943,13 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
with urls_lock:
|
||||
urls_in_progress.discard(url)
|
||||
logger.debug(f"URL supprimée du set des téléchargements en cours: {url} (URLs restantes: {len(urls_in_progress)})")
|
||||
|
||||
# Libérer le slot de la queue
|
||||
try:
|
||||
notify_download_finished()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result[0], result[1]
|
||||
file_found = True
|
||||
|
||||
@@ -975,6 +992,16 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
logger.info(f"Un fichier avec le même nom de base existe déjà: {existing_path}, téléchargement ignoré")
|
||||
result[0] = True
|
||||
result[1] = _("network_download_ok").format(game_name) + _("download_already_extracted")
|
||||
|
||||
# Mettre à jour l'historique
|
||||
for entry in config.history:
|
||||
if entry.get("url") == url:
|
||||
entry["status"] = "Download_OK"
|
||||
entry["progress"] = 100
|
||||
entry["message"] = result[1]
|
||||
save_history(config.history)
|
||||
break
|
||||
|
||||
# Afficher un toast au lieu d'ouvrir l'historique
|
||||
try:
|
||||
show_toast(result[1])
|
||||
@@ -983,6 +1010,13 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
with urls_lock:
|
||||
urls_in_progress.discard(url)
|
||||
logger.debug(f"URL supprimée du set des téléchargements en cours: {url} (URLs restantes: {len(urls_in_progress)})")
|
||||
|
||||
# Libérer le slot de la queue
|
||||
try:
|
||||
notify_download_finished()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result[0], result[1]
|
||||
except Exception as e:
|
||||
logger.debug(f"Erreur lors de la vérification des fichiers existants: {e}")
|
||||
@@ -1209,6 +1243,11 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
|
||||
# Si annulé, ne pas continuer avec extraction
|
||||
if download_canceled:
|
||||
# Libérer le slot de la queue
|
||||
try:
|
||||
notify_download_finished()
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
os.chmod(dest_path, 0o644)
|
||||
@@ -1430,6 +1469,12 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
if url in url_done_events:
|
||||
url_done_events[url].set()
|
||||
|
||||
# Libérer le slot de la queue
|
||||
try:
|
||||
notify_download_finished()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result[0], result[1]
|
||||
|
||||
async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False, task_id=None):
|
||||
|
||||
@@ -29,7 +29,7 @@ def delete_old_files():
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
print(f"Ancien fichier supprimé : {file_path}")
|
||||
print(f"Ancien fichier supprime : {file_path}")
|
||||
logger.info(f"Ancien fichier supprimé : {file_path}")
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la suppression de {file_path} : {str(e)}")
|
||||
@@ -39,7 +39,7 @@ def delete_old_files():
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
print(f"Ancien fichier supprimé : {file_path}")
|
||||
print(f"Ancien fichier supprime : {file_path}")
|
||||
logger.info(f"Ancien fichier supprimé : {file_path}")
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la suppression de {file_path} : {str(e)}")
|
||||
|
||||
@@ -364,7 +364,7 @@ try:
|
||||
logger.info("Test d'écriture dans le fichier de log réussi")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du test d'écriture : {e}")
|
||||
print(f"ERREUR: Impossible d'écrire dans {config.log_file_web}: {e}", file=sys.stderr)
|
||||
print(f"ERREUR: Impossible d'ecrire dans {config.log_file_web}: {e}", file=sys.stderr)
|
||||
|
||||
# Initialiser les données au démarrage
|
||||
logger.info("Chargement initial des données...")
|
||||
@@ -772,7 +772,7 @@ class RGSXHandler(BaseHTTPRequestHandler):
|
||||
# Lire depuis history.json - filtrer seulement les téléchargements en cours
|
||||
history = load_history() or []
|
||||
|
||||
print(f"\n[DEBUG PROGRESS] history.json chargé avec {len(history)} entrées totales")
|
||||
print(f"\n[DEBUG PROGRESS] history.json charge avec {len(history)} entrees totales")
|
||||
|
||||
# Filtrer les entrées avec status "Downloading", "Téléchargement", "Connecting", "Try X/Y"
|
||||
in_progress_statuses = ["Downloading", "Téléchargement", "Downloading", "Connecting", "Extracting"]
|
||||
@@ -797,9 +797,9 @@ class RGSXHandler(BaseHTTPRequestHandler):
|
||||
else:
|
||||
# Debug: afficher les premiers status qui ne matchent pas
|
||||
if len(downloads) < 3:
|
||||
print(f" [DEBUG] Ignoré - Status: '{status}', Game: {entry.get('game_name', '')[:50]}")
|
||||
print(f" [DEBUG] Ignore - Status: '{status}', Game: {entry.get('game_name', '')[:50]}")
|
||||
|
||||
print(f"[DEBUG PROGRESS] {len(downloads)} téléchargements en cours trouvés")
|
||||
print(f"[DEBUG PROGRESS] {len(downloads)} telechargements en cours trouves")
|
||||
if downloads:
|
||||
for url, data in list(downloads.items())[:2]:
|
||||
print(f" - URL: {url[:80]}...")
|
||||
@@ -2088,7 +2088,7 @@ def run_server(host='0.0.0.0', port=5000):
|
||||
if __name__ == '__main__':
|
||||
print("="*60, flush=True)
|
||||
print("Demarrage du serveur RGSX Web...", flush=True)
|
||||
print(f"Fichier de log prévu: {config.log_file_web}", flush=True)
|
||||
print(f"Fichier de log prevu: {config.log_file_web}", flush=True)
|
||||
print("="*60, flush=True)
|
||||
|
||||
parser = argparse.ArgumentParser(description='RGSX Web Server')
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2.3.3.1"
|
||||
"version": "2.3.3.3"
|
||||
}
|
||||
Reference in New Issue
Block a user