v2.0.0.0 - important update to game list structure and reading . you can now add a json and it will be automatically added to gamelist. Added menu to filter systems showing in main screen. Add size showing if available on game list view.
add custom game list source (advanced users only
This commit is contained in:
@@ -265,6 +265,12 @@ async def main():
|
||||
#logger.debug(f"Événement transmis à handle_controls dans pause_menu: {event.type}")
|
||||
continue
|
||||
|
||||
# Gestion des événements pour le menu de filtrage des plateformes
|
||||
if config.menu_state == "filter_platforms":
|
||||
action = handle_controls(event, sources, joystick, screen)
|
||||
config.needs_redraw = True
|
||||
continue
|
||||
|
||||
if config.menu_state == "accessibility_menu":
|
||||
from accessibility import handle_accessibility_events
|
||||
if handle_accessibility_events(event):
|
||||
@@ -342,9 +348,14 @@ async def main():
|
||||
logger.debug("Action quit détectée, arrêt de l'application")
|
||||
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]["name"] # Utiliser le nom de la plateforme
|
||||
url = game[1] if isinstance(game, (list, tuple)) and len(game) > 1 else None
|
||||
if isinstance(game, (list, tuple)):
|
||||
game_name = game[0]
|
||||
url = game[1] if len(game) > 1 else None
|
||||
else: # fallback str
|
||||
game_name = str(game)
|
||||
url = None
|
||||
# Nouveau schéma: config.platforms contient déjà platform_name (string)
|
||||
platform = config.platforms[config.current_platform]
|
||||
if url:
|
||||
logger.debug(f"Vérification pour {game_name}, URL: {url}")
|
||||
# Ajouter une entrée temporaire à l'historique
|
||||
@@ -422,8 +433,12 @@ async def main():
|
||||
platform = entry["platform"]
|
||||
game_name = entry["game_name"]
|
||||
for game in config.games:
|
||||
if game[0] == game_name and config.platforms[config.current_platform] == platform:
|
||||
url = game[1]
|
||||
if isinstance(game, (list, tuple)) and game and game[0] == game_name and config.platforms[config.current_platform] == platform:
|
||||
url = game[1] if len(game) > 1 else None
|
||||
else:
|
||||
continue
|
||||
if not url:
|
||||
continue
|
||||
logger.debug(f"Vérification pour retéléchargement de {game_name}, URL: {url}")
|
||||
if is_1fichier_url(url):
|
||||
if not config.API_KEY_1FICHIER:
|
||||
@@ -604,6 +619,9 @@ async def main():
|
||||
elif config.menu_state == "pause_menu":
|
||||
draw_pause_menu(screen, config.selected_option)
|
||||
#logger.debug("Rendu de draw_pause_menu")
|
||||
elif config.menu_state == "filter_platforms":
|
||||
from display import draw_filter_platforms_menu
|
||||
draw_filter_platforms_menu(screen)
|
||||
elif config.menu_state == "controls_help":
|
||||
draw_controls_help(screen, config.previous_menu_state)
|
||||
elif config.menu_state == "history":
|
||||
|
||||
Binary file not shown.
BIN
ports/RGSX/assets/unrar.exe
Normal file
BIN
ports/RGSX/assets/unrar.exe
Normal file
Binary file not shown.
BIN
ports/RGSX/assets/xdvdfs
Normal file
BIN
ports/RGSX/assets/xdvdfs
Normal file
Binary file not shown.
@@ -2,10 +2,10 @@ import pygame # type: ignore
|
||||
import os
|
||||
import logging
|
||||
import platform
|
||||
from rgsx_settings import load_rgsx_settings, save_rgsx_settings, migrate_old_settings
|
||||
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "1.9.9.4"
|
||||
app_version = "2.0.0.0"
|
||||
|
||||
def get_operating_system():
|
||||
"""Renvoie le nom du système d'exploitation."""
|
||||
@@ -85,28 +85,22 @@ GAMELISTXML = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(APP_F
|
||||
UPDATE_FOLDER = os.path.join(APP_FOLDER, "update")
|
||||
LANGUAGES_FOLDER = os.path.join(APP_FOLDER, "languages")
|
||||
JSON_EXTENSIONS = os.path.join(APP_FOLDER, "rom_extensions.json")
|
||||
MUSIC_FOLDER = os.path.join(APP_FOLDER, "assets", "music")
|
||||
|
||||
#Dossier /saves/ports/rgsx
|
||||
IMAGES_FOLDER = os.path.join(SAVE_FOLDER, "images", "systemes")
|
||||
IMAGES_FOLDER = os.path.join(SAVE_FOLDER, "images")
|
||||
GAMES_FOLDER = os.path.join(SAVE_FOLDER, "games")
|
||||
SOURCES_FILE = os.path.join(SAVE_FOLDER, "sources.json")
|
||||
SOURCES_FILE = os.path.join(SAVE_FOLDER, "systems_list.json")
|
||||
CONTROLS_CONFIG_PATH = os.path.join(SAVE_FOLDER, "controls.json")
|
||||
HISTORY_PATH = os.path.join(SAVE_FOLDER, "history.json")
|
||||
|
||||
# Nouveau fichier unifié pour les paramètres RGSX
|
||||
API_KEY_1FICHIER = os.path.join(SAVE_FOLDER, "1fichierAPI.txt")
|
||||
RGSX_SETTINGS_PATH = os.path.join(SAVE_FOLDER, "rgsx_settings.json")
|
||||
|
||||
# Anciens chemins des fichiers de config (conservés temporairement pour la migration)
|
||||
ACCESSIBILITY_FOLDER = os.path.join(SAVE_FOLDER, "accessibility.json")
|
||||
LANGUAGE_CONFIG_PATH = os.path.join(SAVE_FOLDER, "language.json")
|
||||
MUSIC_CONFIG_PATH = os.path.join(SAVE_FOLDER, "music_config.json")
|
||||
SYMLINK_SETTINGS_PATH = os.path.join(SAVE_FOLDER, "symlink_settings.json")
|
||||
|
||||
# URL
|
||||
OTA_SERVER_URL = "https://retrogamesets.fr/softs/"
|
||||
OTA_VERSION_ENDPOINT = os.path.join(OTA_SERVER_URL, "version.json")
|
||||
OTA_UPDATE_ZIP = os.path.join(OTA_SERVER_URL, "RGSX.zip")
|
||||
OTA_data_ZIP = os.path.join(OTA_SERVER_URL, "rgsx-data.zip")
|
||||
OTA_data_ZIP = os.path.join(OTA_SERVER_URL, "test.zip")
|
||||
|
||||
#CHEMINS DES EXECUTABLES
|
||||
UNRAR_EXE = os.path.join(APP_FOLDER,"assets", "unrar.exe")
|
||||
@@ -197,6 +191,12 @@ batch_download_indices = [] # File d'attente des indices de jeux à traiter en
|
||||
batch_in_progress = False # Indique qu'un lot est en cours
|
||||
batch_pending_game = None # Données du jeu en attente de confirmation d'extension
|
||||
|
||||
# --- Filtre plateformes (UI) ---
|
||||
selected_filter_index = 0 # index dans la liste visible triée
|
||||
filter_platforms_scroll_offset = 0 # défilement si liste longue
|
||||
filter_platforms_dirty = False # indique si modifications non sauvegardées
|
||||
filter_platforms_selection = [] # copie de travail des plateformes visibles (bool masque?) structure: list of (name, hidden_bool)
|
||||
|
||||
|
||||
GRID_COLS = 3 # Number of columns in the platform grid
|
||||
GRID_ROWS = 4 # Number of rows in the platform grid
|
||||
|
||||
@@ -32,7 +32,7 @@ VALID_STATES = [
|
||||
"platform", "game", "confirm_exit",
|
||||
"extension_warning", "pause_menu", "controls_help", "history", "controls_mapping",
|
||||
"redownload_game_cache", "restart_popup", "error", "loading", "confirm_clear_history",
|
||||
"language_select"
|
||||
"language_select", "filter_platforms"
|
||||
]
|
||||
|
||||
def validate_menu_state(state):
|
||||
@@ -1036,7 +1036,13 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.menu_state = "accessibility_menu"
|
||||
config.needs_redraw = True
|
||||
logger.debug("Passage au menu accessibilité")
|
||||
elif config.selected_option == 5: # Source toggle
|
||||
elif config.selected_option == 5: # Filter platforms
|
||||
# Ne pas écraser previous_menu_state; il référence l'état avant l'ouverture du pause menu
|
||||
config.menu_state = "filter_platforms"
|
||||
config.selected_filter_index = 0
|
||||
config.filter_platforms_scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 6: # Source toggle (index shifted by new option)
|
||||
try:
|
||||
from rgsx_settings import get_sources_mode, set_sources_mode
|
||||
current_mode = get_sources_mode()
|
||||
@@ -1052,13 +1058,13 @@ def handle_controls(event, sources, joystick, screen):
|
||||
logger.info(f"Changement du mode des sources vers {new_mode}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur changement mode sources: {e}")
|
||||
elif config.selected_option == 6: # Redownload game cache
|
||||
elif config.selected_option == 7: # Redownload game cache
|
||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.menu_state = "redownload_game_cache"
|
||||
config.redownload_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Passage à redownload_game_cache depuis pause_menu")
|
||||
elif config.selected_option == 7: # Music toggle
|
||||
elif config.selected_option == 8: # Music toggle
|
||||
config.music_enabled = not config.music_enabled
|
||||
save_music_config()
|
||||
if config.music_enabled:
|
||||
@@ -1070,7 +1076,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
pygame.mixer.music.stop()
|
||||
config.needs_redraw = True
|
||||
logger.info(f"Musique {'activée' if config.music_enabled else 'désactivée'} via menu pause")
|
||||
elif config.selected_option == 8: # Symlink option
|
||||
elif config.selected_option == 9: # Symlink option
|
||||
from rgsx_settings import set_symlink_option, get_symlink_option
|
||||
current_status = get_symlink_option()
|
||||
success, message = set_symlink_option(not current_status)
|
||||
@@ -1078,7 +1084,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.popup_timer = 3000 if success else 5000
|
||||
config.needs_redraw = True
|
||||
logger.info(f"Symlink option {'activée' if not current_status else 'désactivée'} via menu pause")
|
||||
elif config.selected_option == 9: # Quit
|
||||
elif config.selected_option == 10: # Quit
|
||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.menu_state = "confirm_exit"
|
||||
config.confirm_selection = 0
|
||||
@@ -1117,8 +1123,12 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.pending_download = None
|
||||
if os.path.exists(config.SOURCES_FILE):
|
||||
try:
|
||||
os.remove(config.SOURCES_FILE)
|
||||
logger.debug("Fichier sources.json supprimé avec succès")
|
||||
if os.path.exists(config.SOURCES_FILE):
|
||||
os.remove(config.SOURCES_FILE)
|
||||
logger.debug("Fichier system_list.json supprimé avec succès")
|
||||
if os.path.exists(config.SAVE_FOLDER + "/sources.json"):
|
||||
os.remove(config.SAVE_FOLDER + "/sources.json")
|
||||
logger.debug("Fichier sources.json supprimé avec succès")
|
||||
if os.path.exists(config.GAMES_FOLDER):
|
||||
shutil.rmtree(config.GAMES_FOLDER)
|
||||
logger.debug("Dossier games supprimé avec succès")
|
||||
@@ -1203,6 +1213,71 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
logger.debug("Annulation de la sélection de langue, retour au menu pause")
|
||||
|
||||
# Menu filtre plateformes
|
||||
elif config.menu_state == "filter_platforms":
|
||||
total_items = len(config.filter_platforms_selection)
|
||||
action_buttons = 4
|
||||
extended_max = total_items + action_buttons - 1
|
||||
if is_input_matched(event, "up"):
|
||||
if config.selected_filter_index > 0:
|
||||
config.selected_filter_index -= 1
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
# Wrap vers les boutons (premier bouton) depuis le haut
|
||||
if total_items > 0:
|
||||
config.selected_filter_index = total_items
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "down"):
|
||||
if config.selected_filter_index < extended_max:
|
||||
config.selected_filter_index += 1
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
# Wrap retour en haut de la liste
|
||||
config.selected_filter_index = 0
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "left"):
|
||||
if config.selected_filter_index >= total_items:
|
||||
if config.selected_filter_index > total_items:
|
||||
config.selected_filter_index -= 1
|
||||
config.needs_redraw = True
|
||||
# sinon ignorer
|
||||
elif is_input_matched(event, "right"):
|
||||
if config.selected_filter_index >= total_items:
|
||||
if config.selected_filter_index < extended_max:
|
||||
config.selected_filter_index += 1
|
||||
config.needs_redraw = True
|
||||
# sinon ignorer
|
||||
elif is_input_matched(event, "confirm"):
|
||||
if config.selected_filter_index < total_items:
|
||||
name, hidden = config.filter_platforms_selection[config.selected_filter_index]
|
||||
config.filter_platforms_selection[config.selected_filter_index] = (name, not hidden)
|
||||
config.filter_platforms_dirty = True
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
btn_idx = config.selected_filter_index - total_items
|
||||
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
|
||||
from utils import load_sources
|
||||
settings = load_rgsx_settings()
|
||||
if btn_idx == 0: # all visible
|
||||
config.filter_platforms_selection = [(n, False) for n, _ in config.filter_platforms_selection]
|
||||
config.filter_platforms_dirty = True
|
||||
elif btn_idx == 1: # none visible
|
||||
config.filter_platforms_selection = [(n, True) for n, _ in config.filter_platforms_selection]
|
||||
config.filter_platforms_dirty = True
|
||||
elif btn_idx == 2: # apply
|
||||
hidden_list = [n for n, h in config.filter_platforms_selection if h]
|
||||
settings["hidden_platforms"] = hidden_list
|
||||
save_rgsx_settings(settings)
|
||||
load_sources()
|
||||
config.filter_platforms_dirty = False
|
||||
config.menu_state = "pause_menu"
|
||||
elif btn_idx == 3: # back
|
||||
config.menu_state = "pause_menu"
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "cancel"):
|
||||
config.menu_state = "pause_menu"
|
||||
config.needs_redraw = True
|
||||
|
||||
|
||||
# Gestion des relâchements de touches
|
||||
if event.type == pygame.KEYUP:
|
||||
|
||||
@@ -83,8 +83,20 @@ def draw_stylized_button(screen, text, x, y, width, height, selected=False):
|
||||
|
||||
# Transition d'image lors de la sélection d'un système
|
||||
def draw_validation_transition(screen, platform_index):
|
||||
"""Affiche une animation de transition fluide pour la sélection d’une plateforme."""
|
||||
platform_dict = config.platform_dicts[platform_index]
|
||||
"""Affiche une animation de transition fluide pour la sélection d’une plateforme.
|
||||
Utilise le mapping par nom pour éviter les décalages d'image si l'ordre d'affichage
|
||||
diffère de l'ordre de stockage."""
|
||||
# Récupérer le nom affiché correspondant à l'index trié
|
||||
if platform_index < 0 or platform_index >= len(config.platforms):
|
||||
return
|
||||
platform_name = config.platforms[platform_index]
|
||||
platform_dict = getattr(config, 'platform_dict_by_name', {}).get(platform_name)
|
||||
if not platform_dict:
|
||||
# Fallback index direct si mapping absent
|
||||
try:
|
||||
platform_dict = config.platform_dicts[platform_index]
|
||||
except Exception:
|
||||
return
|
||||
image = load_system_image(platform_dict)
|
||||
if not image:
|
||||
return
|
||||
@@ -468,8 +480,16 @@ def draw_platform_grid(screen):
|
||||
scale_base = 1.5 if is_selected else 1.0
|
||||
scale = scale_base + pulse if is_selected else scale_base
|
||||
|
||||
platform_dict = config.platform_dicts[idx]
|
||||
platform_id = platform_dict.get("platform", str(idx))
|
||||
# Récupération robuste du dict via nom
|
||||
display_name = config.platforms[idx]
|
||||
platform_dict = getattr(config, 'platform_dict_by_name', {}).get(display_name)
|
||||
if not platform_dict:
|
||||
# Fallback index brut
|
||||
if idx < len(config.platform_dicts):
|
||||
platform_dict = config.platform_dicts[idx]
|
||||
else:
|
||||
continue
|
||||
platform_id = platform_dict.get("platform_name") or platform_dict.get("platform") or display_name
|
||||
|
||||
# Utiliser le cache d'images pour éviter de recharger/redimensionner à chaque frame
|
||||
cache_key = f"{platform_id}_{scale:.2f}"
|
||||
@@ -570,15 +590,17 @@ def draw_game_list(screen):
|
||||
return
|
||||
|
||||
line_height = config.small_font.get_height() + 10
|
||||
header_height = line_height # hauteur de l'en-tête identique à une ligne
|
||||
margin_top_bottom = 20
|
||||
extra_margin_top = 20
|
||||
extra_margin_bottom = 60
|
||||
title_height = config.title_font.get_height() + 20
|
||||
|
||||
available_height = config.screen_height - title_height - extra_margin_top - extra_margin_bottom - 2 * margin_top_bottom
|
||||
items_per_page = available_height // line_height
|
||||
# Réserver de l'espace pour l'en-tête (header_height)
|
||||
available_height = config.screen_height - title_height - extra_margin_top - extra_margin_bottom - 2 * margin_top_bottom - header_height
|
||||
items_per_page = max(1, available_height // line_height)
|
||||
|
||||
rect_height = items_per_page * line_height + 2 * margin_top_bottom
|
||||
rect_height = header_height + items_per_page * line_height + 2 * margin_top_bottom
|
||||
rect_width = int(0.95 * config.screen_width)
|
||||
rect_x = (config.screen_width - rect_width) // 2
|
||||
rect_y = title_height + extra_margin_top + (config.screen_height - title_height - extra_margin_top - extra_margin_bottom - rect_height) // 2
|
||||
@@ -622,21 +644,60 @@ def draw_game_list(screen):
|
||||
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)
|
||||
|
||||
# Largeur colonne taille (15%) mini 120px, reste pour nom
|
||||
size_col_width = max(120, int(rect_width * 0.15))
|
||||
name_col_width = rect_width - 40 - size_col_width # padding horizontal 40
|
||||
|
||||
# ---- En-tête ----
|
||||
header_name = _("game_header_name")
|
||||
header_size = _("game_header_size")
|
||||
header_y_center = rect_y + margin_top_bottom + header_height // 2
|
||||
# Nom aligné gauche
|
||||
header_name_surface = config.small_font.render(header_name, True, THEME_COLORS["text"])
|
||||
header_name_rect = header_name_surface.get_rect()
|
||||
header_name_rect.midleft = (rect_x + 20, header_y_center)
|
||||
# Taille alignée droite
|
||||
header_size_surface = config.small_font.render(header_size, True, THEME_COLORS["text"])
|
||||
header_size_rect = header_size_surface.get_rect()
|
||||
header_size_rect.midright = (rect_x + rect_width - 20, header_y_center)
|
||||
screen.blit(header_name_surface, header_name_rect)
|
||||
screen.blit(header_size_surface, header_size_rect)
|
||||
# Ligne de séparation sous l'en-tête
|
||||
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)
|
||||
|
||||
# Position de départ des lignes après l'en-tête
|
||||
list_start_y = rect_y + margin_top_bottom + header_height
|
||||
|
||||
for i in range(config.scroll_offset, min(config.scroll_offset + items_per_page, len(games))):
|
||||
game_name = games[i][0] if isinstance(games[i], (list, tuple)) else games[i]
|
||||
item = games[i]
|
||||
if isinstance(item, (list, tuple)) and item:
|
||||
game_name = item[0]
|
||||
size_val = item[2] if len(item) > 2 else None
|
||||
else:
|
||||
game_name = str(item)
|
||||
size_val = None
|
||||
size_text = size_val if (isinstance(size_val, str) and size_val.strip()) else "N/A"
|
||||
is_marked = i in getattr(config, 'selected_games', set())
|
||||
# Couleur verte si jeu sous curseur OU marqué en multi-sélection
|
||||
color = THEME_COLORS["fond_lignes"] if (i == config.current_game or is_marked) else THEME_COLORS["text"]
|
||||
# Préfixe ASCII pour distinguer les jeux marqués (éviter collision glyphes)
|
||||
prefix = "[X] " if is_marked else " "
|
||||
game_text = truncate_text_middle(prefix + game_name, config.small_font, rect_width - 40, is_filename=False)
|
||||
text_surface = config.small_font.render(game_text, True, color)
|
||||
text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + (i - config.scroll_offset) * line_height + line_height // 2))
|
||||
truncated_name = truncate_text_middle(prefix + game_name, config.small_font, name_col_width, is_filename=False)
|
||||
name_surface = config.small_font.render(truncated_name, True, color)
|
||||
size_surface = config.small_font.render(size_text, True, THEME_COLORS["text"])
|
||||
row_center_y = list_start_y + (i - config.scroll_offset) * line_height + line_height // 2
|
||||
# Position nom (aligné à gauche dans la boite)
|
||||
name_rect = name_surface.get_rect()
|
||||
name_rect.midleft = (rect_x + 20, row_center_y)
|
||||
size_rect = size_surface.get_rect()
|
||||
size_rect.midright = (rect_x + rect_width - 20, row_center_y)
|
||||
if i == config.current_game:
|
||||
glow_surface = pygame.Surface((text_rect.width + 20, text_rect.height + 10), pygame.SRCALPHA)
|
||||
pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (10, 5, text_rect.width, text_rect.height), border_radius=8)
|
||||
screen.blit(glow_surface, (text_rect.left - 10, text_rect.top - 5))
|
||||
screen.blit(text_surface, text_rect)
|
||||
glow_width = rect_width - 40
|
||||
glow_height = name_rect.height + 10
|
||||
glow_surface = pygame.Surface((glow_width, glow_height), pygame.SRCALPHA)
|
||||
pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (0, 0, glow_width, glow_height), border_radius=8)
|
||||
screen.blit(glow_surface, (rect_x + 20, row_center_y - glow_height // 2))
|
||||
screen.blit(name_surface, name_rect)
|
||||
screen.blit(size_surface, size_rect)
|
||||
|
||||
if len(games) > items_per_page:
|
||||
try:
|
||||
@@ -1202,6 +1263,7 @@ def draw_pause_menu(screen, selected_option):
|
||||
_("menu_history"), # 2
|
||||
_("menu_language"), # 3
|
||||
_("menu_accessibility"), # 4
|
||||
_("menu_filter_platforms"), # 5 new filter option
|
||||
f"{_('menu_games_source_prefix')}: {source_label}", # 5
|
||||
_("menu_redownload_cache"), # 6
|
||||
music_option, # 7
|
||||
@@ -1232,6 +1294,116 @@ def draw_pause_menu(screen, selected_option):
|
||||
# Stocker le nombre total d'options pour la navigation dynamique
|
||||
config.pause_menu_total_options = len(options)
|
||||
|
||||
def draw_filter_platforms_menu(screen):
|
||||
"""Affiche le menu de filtrage des plateformes (afficher/masquer)."""
|
||||
from rgsx_settings import load_rgsx_settings
|
||||
screen.blit(OVERLAY, (0, 0))
|
||||
settings = load_rgsx_settings()
|
||||
hidden = set(settings.get("hidden_platforms", [])) if isinstance(settings, dict) else set()
|
||||
|
||||
# Initialiser la copie de travail si vide ou taille différente
|
||||
if not config.filter_platforms_selection or len(config.filter_platforms_selection) != len(config.platform_dicts):
|
||||
# Liste alphabétique complète (sans filtrer hidden existant)
|
||||
all_names = sorted([d.get("platform_name", "") for d in config.platform_dicts if d.get("platform_name")])
|
||||
config.filter_platforms_selection = [(name, name in hidden) for name in all_names]
|
||||
config.selected_filter_index = 0
|
||||
config.filter_platforms_scroll_offset = 0
|
||||
config.filter_platforms_dirty = False
|
||||
|
||||
title_text = _("filter_platforms_title")
|
||||
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(80, 40)
|
||||
title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10)
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12)
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||||
screen.blit(title_surface, title_rect)
|
||||
|
||||
# Zone liste
|
||||
list_width = int(config.screen_width * 0.7)
|
||||
list_height = int(config.screen_height * 0.6)
|
||||
list_x = (config.screen_width - list_width) // 2
|
||||
list_y = title_rect_inflated.bottom + 20
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (list_x, list_y, list_width, list_height), border_radius=12)
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], (list_x, list_y, list_width, list_height), 2, border_radius=12)
|
||||
|
||||
line_height = config.small_font.get_height() + 8
|
||||
visible_items = list_height // line_height - 1 # laisser un peu d'espace bas
|
||||
total_items = len(config.filter_platforms_selection)
|
||||
if config.selected_filter_index < 0:
|
||||
config.selected_filter_index = 0
|
||||
# Ne pas forcer la réduction si on est sur les boutons (indices >= total_items)
|
||||
# Laisser controls.py gérer la borne max étendue
|
||||
# Ajuster scroll
|
||||
if config.selected_filter_index < config.filter_platforms_scroll_offset:
|
||||
config.filter_platforms_scroll_offset = config.selected_filter_index
|
||||
elif config.selected_filter_index >= config.filter_platforms_scroll_offset + visible_items:
|
||||
config.filter_platforms_scroll_offset = config.selected_filter_index - visible_items + 1
|
||||
|
||||
# Dessiner items
|
||||
for i in range(config.filter_platforms_scroll_offset, min(config.filter_platforms_scroll_offset + visible_items, total_items)):
|
||||
name, is_hidden = config.filter_platforms_selection[i]
|
||||
idx_on_screen = i - config.filter_platforms_scroll_offset
|
||||
y_center = list_y + 10 + idx_on_screen * line_height + line_height // 2
|
||||
selected = (i == config.selected_filter_index)
|
||||
checkbox = "[ ]" if is_hidden else "[X]" # inversé: coché signifie visible
|
||||
# Correction: on veut [X] si visible => is_hidden False
|
||||
checkbox = "[X]" if not is_hidden else "[ ]"
|
||||
display_text = f"{checkbox} {name}"
|
||||
color = THEME_COLORS["fond_lignes"] if selected else THEME_COLORS["text"]
|
||||
text_surface = config.small_font.render(display_text, True, color)
|
||||
text_rect = text_surface.get_rect(midleft=(list_x + 20, y_center))
|
||||
if selected:
|
||||
glow_surface = pygame.Surface((list_width - 40, line_height), pygame.SRCALPHA)
|
||||
pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (0, 0, list_width - 40, line_height), border_radius=8)
|
||||
screen.blit(glow_surface, (list_x + 20, y_center - line_height // 2))
|
||||
screen.blit(text_surface, text_rect)
|
||||
|
||||
# Scrollbar
|
||||
if total_items > visible_items:
|
||||
scroll_height = int((visible_items / total_items) * (list_height - 20))
|
||||
scroll_y = int((config.filter_platforms_scroll_offset / max(1, total_items - visible_items)) * (list_height - 20 - scroll_height))
|
||||
pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], (list_x + list_width - 25, list_y + 10 + scroll_y, 10, scroll_height), border_radius=4)
|
||||
|
||||
# Boutons d'action
|
||||
btn_width = 220
|
||||
btn_height = int(config.screen_height * 0.0463)
|
||||
spacing = 30
|
||||
buttons_y = list_y + list_height + 20
|
||||
center_x = config.screen_width // 2
|
||||
actions = [
|
||||
("filter_all", -2),
|
||||
("filter_none", -3),
|
||||
("filter_apply", -4),
|
||||
("filter_back", -5)
|
||||
]
|
||||
# Indice spécial sélection boutons quand selected_filter_index >= total_items
|
||||
extra_index_base = total_items
|
||||
# Ajuster selected_filter_index max pour inclure boutons
|
||||
extended_max = total_items + len(actions) - 1
|
||||
if config.selected_filter_index > extended_max:
|
||||
config.selected_filter_index = extended_max
|
||||
|
||||
for idx, (key, offset) in enumerate(actions):
|
||||
btn_x = center_x - (len(actions) * (btn_width + spacing) - spacing) // 2 + idx * (btn_width + spacing)
|
||||
is_selected = (config.selected_filter_index == total_items + idx)
|
||||
label = _(key)
|
||||
draw_stylized_button(screen, label, btn_x, buttons_y, btn_width, btn_height, selected=is_selected)
|
||||
|
||||
# Infos bas
|
||||
hidden_count = sum(1 for _, h in config.filter_platforms_selection if h)
|
||||
visible_count = total_items - hidden_count
|
||||
info_text = _("filter_platforms_info").format(visible_count, hidden_count, total_items)
|
||||
info_surface = config.small_font.render(info_text, True, THEME_COLORS["text"])
|
||||
info_rect = info_surface.get_rect(center=(config.screen_width // 2, buttons_y + btn_height + 30))
|
||||
screen.blit(info_surface, info_rect)
|
||||
|
||||
if config.filter_platforms_dirty:
|
||||
dirty_text = _("filter_unsaved_warning")
|
||||
dirty_surface = config.small_font.render(dirty_text, True, THEME_COLORS["warning_text"])
|
||||
dirty_rect = dirty_surface.get_rect(center=(config.screen_width // 2, info_rect.bottom + 25))
|
||||
screen.blit(dirty_surface, dirty_rect)
|
||||
|
||||
# Menu aide contrôles
|
||||
def draw_controls_help(screen, previous_state):
|
||||
"""Affiche la liste des contrôles (aide) avec mise en page adaptative."""
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"error_api_key": "Achtung, du musst deinen API-Schlüssel (nur Premium) in der Datei {0} eingeben",
|
||||
"error_api_key_extended": "Achtung, du musst deinen API-Schlüssel (nur Premium) in der Datei /userdata/saves/ports/rgsx/1fichierAPI.txt einfügen. Öffne die Datei in einem Texteditor und füge den API-Schlüssel ein",
|
||||
"error_invalid_download_data": "Ungültige Downloaddaten",
|
||||
"error_delete_sources": "Fehler beim Löschen der Datei sources.json oder Ordner",
|
||||
"error_delete_sources": "Fehler beim Löschen der Datei systems_list.json oder Ordner",
|
||||
"error_extension": "Nicht unterstützte Erweiterung oder Downloadfehler",
|
||||
"error_no_download": "Keine Downloads ausstehend.",
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
"game_count": "{0} ({1} Spiele)",
|
||||
"game_filter": "Aktiver Filter: {0}",
|
||||
"game_search": "Filtern: {0}",
|
||||
"game_header_name": "Name",
|
||||
"game_header_size": "Größe",
|
||||
|
||||
"history_title": "Downloads ({0})",
|
||||
"history_empty": "Keine Downloads im Verlauf",
|
||||
@@ -78,6 +80,14 @@
|
||||
"menu_music_toggle": "Musik ein/aus",
|
||||
"menu_music_enabled": "Musik aktiviert: {0}",
|
||||
"menu_music_disabled": "Musik deaktiviert",
|
||||
"menu_filter_platforms": "Systeme filtern",
|
||||
"filter_platforms_title": "Systemsichtbarkeit",
|
||||
"filter_all": "Alle anzeigen",
|
||||
"filter_none": "Alle ausblenden",
|
||||
"filter_apply": "Anwenden",
|
||||
"filter_back": "Zurück",
|
||||
"filter_platforms_info": "Sichtbar: {0} | Versteckt: {1} / Gesamt: {2}",
|
||||
"filter_unsaved_warning": "Ungespeicherte Änderungen",
|
||||
"menu_quit": "Beenden",
|
||||
|
||||
"button_yes": "Ja",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"error_api_key": "Please enter your API key (premium only) in the file {0}",
|
||||
"error_api_key_extended": "Please enter your API key (premium only) in the file /userdata/saves/ports/rgsx/1fichierAPI.txt by opening it in a text editor and pasting your API key",
|
||||
"error_invalid_download_data": "Invalid download data",
|
||||
"error_delete_sources": "Error deleting sources.json file or folders",
|
||||
"error_delete_sources": "Error deleting systems_list.json file or folders",
|
||||
"error_extension": "Unsupported extension or download error",
|
||||
"error_no_download": "No pending download.",
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
"game_count": "{0} ({1} games)",
|
||||
"game_filter": "Active filter: {0}",
|
||||
"game_search": "Filter: {0}",
|
||||
"game_header_name": "Name",
|
||||
"game_header_size": "Size",
|
||||
|
||||
"history_title": "Downloads ({0})",
|
||||
"history_empty": "No downloads in history",
|
||||
@@ -78,6 +80,14 @@
|
||||
"menu_music_toggle": "Toggle music",
|
||||
"menu_music_enabled": "Music enabled: {0}",
|
||||
"menu_music_disabled": "Music disabled",
|
||||
"menu_filter_platforms": "Filter systems",
|
||||
"filter_platforms_title": "Systems visibility",
|
||||
"filter_all": "Show all",
|
||||
"filter_none": "Hide all",
|
||||
"filter_apply": "Apply",
|
||||
"filter_back": "Back",
|
||||
"filter_platforms_info": "Visible: {0} | Hidden: {1} / Total: {2}",
|
||||
"filter_unsaved_warning": "Unsaved changes",
|
||||
"menu_quit": "Quit",
|
||||
|
||||
"button_yes": "Yes",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"error_api_key": "Atención, debes ingresar tu clave API (solo premium) en el archivo {0}",
|
||||
"error_api_key_extended": "Atención, debes ingresar tu clave API (solo premium) en el archivo /userdata/saves/ports/rgsx/1fichierAPI.txt, abrirlo en un editor de texto y pegar la clave API",
|
||||
"error_invalid_download_data": "Datos de descarga no válidos",
|
||||
"error_delete_sources": "Error al eliminar el archivo sources.json o carpetas",
|
||||
"error_delete_sources": "Error al eliminar el archivo systems_list.json o carpetas",
|
||||
"error_extension": "Extensión no soportada o error de descarga",
|
||||
"error_no_download": "No hay descargas pendientes.",
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
"game_count": "{0} ({1} juegos)",
|
||||
"game_filter": "Filtro activo: {0}",
|
||||
"game_search": "Filtrar: {0}",
|
||||
"game_header_name": "Nombre",
|
||||
"game_header_size": "Tamaño",
|
||||
|
||||
"history_title": "Descargas ({0})",
|
||||
"history_empty": "No hay descargas en el historial",
|
||||
@@ -79,6 +81,14 @@
|
||||
"menu_music_toggle": "Activar/Desactivar música",
|
||||
"menu_music_enabled": "Música activada: {0}",
|
||||
"menu_music_disabled": "Música desactivada",
|
||||
"menu_filter_platforms": "Filtrar sistemas",
|
||||
"filter_platforms_title": "Visibilidad de sistemas",
|
||||
"filter_all": "Mostrar todo",
|
||||
"filter_none": "Ocultar todo",
|
||||
"filter_apply": "Aplicar",
|
||||
"filter_back": "Volver",
|
||||
"filter_platforms_info": "Visibles: {0} | Ocultos: {1} / Total: {2}",
|
||||
"filter_unsaved_warning": "Cambios no guardados",
|
||||
"menu_quit": "Salir",
|
||||
|
||||
"button_yes": "Sí",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"error_api_key": "Attention il faut renseigner sa clé API (premium only) dans le fichier {0}",
|
||||
"error_api_key_extended": "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",
|
||||
"error_invalid_download_data": "Données de téléchargement invalides",
|
||||
"error_delete_sources": "Erreur lors de la suppression du fichier sources.json ou dossiers",
|
||||
"error_delete_sources": "Erreur lors de la suppression du fichier systems_list.json ou dossiers",
|
||||
"error_extension": "Extension non supportée ou erreur de téléchargement",
|
||||
"error_no_download": "Aucun téléchargement en attente.",
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
"game_count": "{0} ({1} jeux)",
|
||||
"game_filter": "Filtre actif : {0}",
|
||||
"game_search": "Filtrer : {0}",
|
||||
"game_header_name": "Nom",
|
||||
"game_header_size": "Taille",
|
||||
|
||||
"history_title": "Téléchargements ({0})",
|
||||
"history_empty": "Aucun téléchargement dans l'historique",
|
||||
@@ -76,6 +78,14 @@
|
||||
"menu_music_toggle": "Activer/Désactiver la musique",
|
||||
"menu_music_enabled": "Musique activée : {0}",
|
||||
"menu_music_disabled": "Musique désactivée",
|
||||
"menu_filter_platforms": "Filtrer les systèmes",
|
||||
"filter_platforms_title": "Affichage des systèmes",
|
||||
"filter_all": "Tout afficher",
|
||||
"filter_none": "Tout masquer",
|
||||
"filter_apply": "Appliquer",
|
||||
"filter_back": "Retour",
|
||||
"filter_platforms_info": "Visibles: {0} | Masqués: {1} / Total: {2}",
|
||||
"filter_unsaved_warning": "Modifications non sauvegardées",
|
||||
|
||||
"button_yes": "Oui",
|
||||
"button_no": "Non",
|
||||
|
||||
@@ -260,7 +260,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
|
||||
dest_dir = None
|
||||
for platform_dict in config.platform_dicts:
|
||||
if platform_dict["platform"] == platform:
|
||||
if platform_dict.get("platform_name") == platform:
|
||||
platform_folder = platform_dict.get("folder", normalize_platform_name(platform))
|
||||
dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder)
|
||||
logger.debug(f"Répertoire de destination trouvé pour {platform}: {dest_dir}")
|
||||
@@ -298,9 +298,82 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
download_headers = headers.copy()
|
||||
download_headers['Accept'] = 'application/octet-stream, */*'
|
||||
download_headers['Referer'] = 'https://myrient.erista.me/'
|
||||
response = session.get(url, stream=True, timeout=30, allow_redirects=True, headers=download_headers)
|
||||
logger.debug(f"Status code: {response.status_code}")
|
||||
response.raise_for_status()
|
||||
|
||||
# Préparation spécifique archive.org : récupérer quelques pages pour obtenir cookies éventuels
|
||||
if 'archive.org/download/' in url:
|
||||
try:
|
||||
pre_id = url.split('/download/')[1].split('/')[0]
|
||||
session.get('https://archive.org/robots.txt', timeout=10)
|
||||
session.get(f'https://archive.org/metadata/{pre_id}', timeout=10)
|
||||
logger.debug(f"Pré-chargement cookies/metadata archive.org pour {pre_id}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Pré-chargement archive.org ignoré: {e}")
|
||||
# Tentatives multiples avec variations d'en-têtes pour contourner certains 401/403 (archive.org / hotlink protection)
|
||||
header_variants = [
|
||||
download_headers,
|
||||
{ # Variante sans Referer spécifique
|
||||
'User-Agent': headers['User-Agent'],
|
||||
'Accept': 'application/octet-stream,*/*;q=0.8',
|
||||
'Accept-Language': headers['Accept-Language'],
|
||||
'Connection': 'keep-alive'
|
||||
},
|
||||
{ # Variante minimaliste type curl
|
||||
'User-Agent': 'curl/8.4.0',
|
||||
'Accept': '*/*'
|
||||
},
|
||||
{ # Variante avec Referer archive.org
|
||||
'User-Agent': headers['User-Agent'],
|
||||
'Accept': '*/*',
|
||||
'Referer': 'https://archive.org/'
|
||||
}
|
||||
]
|
||||
response = None
|
||||
last_status = None
|
||||
for attempt, hv in enumerate(header_variants, start=1):
|
||||
try:
|
||||
logger.debug(f"Tentative téléchargement {attempt}/{len(header_variants)} avec headers: {hv}")
|
||||
r = session.get(url, stream=True, timeout=30, allow_redirects=True, headers=hv)
|
||||
last_status = r.status_code
|
||||
logger.debug(f"Status code tentative {attempt}: {r.status_code}")
|
||||
if r.status_code in (401, 403):
|
||||
# Lire un petit bout pour voir si message utile
|
||||
try:
|
||||
snippet = r.text[:200]
|
||||
logger.debug(f"Réponse {r.status_code} snippet: {snippet}")
|
||||
except Exception:
|
||||
pass
|
||||
continue # Essayer variante suivante
|
||||
r.raise_for_status()
|
||||
response = r
|
||||
break
|
||||
except requests.RequestException as e:
|
||||
logger.debug(f"Erreur tentative {attempt}: {e}")
|
||||
# Si ce n'est pas une erreur auth explicite et qu'on a un code => on sort
|
||||
if isinstance(e, requests.HTTPError) and last_status not in (401, 403):
|
||||
break
|
||||
if response is None:
|
||||
# Fallback metadata archive.org pour message clair
|
||||
if 'archive.org/download/' in url:
|
||||
try:
|
||||
identifier = url.split('/download/')[1].split('/')[0]
|
||||
meta_resp = session.get(f'https://archive.org/metadata/{identifier}', timeout=15)
|
||||
if meta_resp.status_code == 200:
|
||||
meta_json = meta_resp.json()
|
||||
if meta_json.get('is_dark'):
|
||||
raise requests.HTTPError(f"Item archive.org restreint (is_dark=true): {identifier}")
|
||||
if not meta_json.get('files'):
|
||||
raise requests.HTTPError(f"Item archive.org sans fichiers listés: {identifier}")
|
||||
# Fichier peut avoir un nom différent : informer
|
||||
available = [f.get('name') for f in meta_json.get('files', [])][:10]
|
||||
raise requests.HTTPError(f"Accès refusé (HTTP {last_status}). Fichiers disponibles exemples: {available}")
|
||||
else:
|
||||
raise requests.HTTPError(f"HTTP {last_status} & metadata {meta_resp.status_code} pour {identifier}")
|
||||
except requests.HTTPError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise requests.HTTPError(f"HTTP {last_status} après variations; metadata échec: {e}")
|
||||
auth_msg = f"HTTP {last_status} après variations d'en-têtes" if last_status else "Aucune réponse valide"
|
||||
raise requests.HTTPError(auth_msg)
|
||||
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
logger.debug(f"Taille totale: {total_size} octets")
|
||||
@@ -471,7 +544,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
|
||||
dest_dir = None
|
||||
for platform_dict in config.platform_dicts:
|
||||
if platform_dict["platform"] == platform:
|
||||
if platform_dict.get("platform_name") == platform:
|
||||
platform_folder = platform_dict.get("folder", normalize_platform_name(platform))
|
||||
dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder)
|
||||
break
|
||||
|
||||
@@ -29,9 +29,9 @@ def load_rgsx_settings():
|
||||
"enabled": False,
|
||||
"target_directory": ""
|
||||
},
|
||||
"sources": { # Nouvelle section pour la source des jeux
|
||||
"mode": "rgsx", # "rgsx" ou "custom"
|
||||
"custom_url": "" # URL personnalisée pour le ZIP des sources
|
||||
"sources": {
|
||||
"mode": "rgsx",
|
||||
"custom_url": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,12 +44,6 @@ def load_rgsx_settings():
|
||||
if key not in settings:
|
||||
settings[key] = value
|
||||
return settings
|
||||
else:
|
||||
# Tenter de migrer depuis les anciens fichiers
|
||||
migrated_settings = migrate_old_settings()
|
||||
if migrated_settings:
|
||||
save_rgsx_settings(migrated_settings)
|
||||
return migrated_settings
|
||||
except Exception as e:
|
||||
print(f"Erreur lors du chargement de rgsx_settings.json: {str(e)}")
|
||||
|
||||
@@ -68,96 +62,6 @@ def save_rgsx_settings(settings):
|
||||
print(f"Erreur lors de la sauvegarde de rgsx_settings.json: {str(e)}")
|
||||
|
||||
|
||||
def migrate_old_settings():
|
||||
"""Migre les anciens fichiers de configuration vers le nouveau format."""
|
||||
from config import LANGUAGE_CONFIG_PATH, MUSIC_CONFIG_PATH, ACCESSIBILITY_FOLDER, SYMLINK_SETTINGS_PATH
|
||||
|
||||
migrated_settings = {
|
||||
"language": "en",
|
||||
"music_enabled": True,
|
||||
"accessibility": {
|
||||
"font_scale": 1.0
|
||||
},
|
||||
"symlink": {
|
||||
"enabled": False,
|
||||
"target_directory": ""
|
||||
}
|
||||
}
|
||||
|
||||
files_to_remove = [] # Liste des fichiers à supprimer après migration réussie
|
||||
|
||||
# Migrer language.json
|
||||
if os.path.exists(LANGUAGE_CONFIG_PATH):
|
||||
try:
|
||||
with open(LANGUAGE_CONFIG_PATH, 'r', encoding='utf-8') as f:
|
||||
content = f.read().strip()
|
||||
# Gérer le cas où le fichier contient juste une chaîne (pas de JSON)
|
||||
if content.startswith('"') and content.endswith('"'):
|
||||
migrated_settings["language"] = content.strip('"')
|
||||
elif not content.startswith('{'):
|
||||
# Fichier texte simple sans guillemets
|
||||
migrated_settings["language"] = content
|
||||
else:
|
||||
# Fichier JSON normal
|
||||
lang_data = json.loads(content)
|
||||
migrated_settings["language"] = lang_data.get("language", "en")
|
||||
files_to_remove.append(LANGUAGE_CONFIG_PATH)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Migrer music_config.json
|
||||
if os.path.exists(MUSIC_CONFIG_PATH):
|
||||
try:
|
||||
with open(MUSIC_CONFIG_PATH, 'r', encoding='utf-8') as f:
|
||||
content = f.read().strip()
|
||||
# Gérer le cas où le fichier contient juste un booléen
|
||||
if content.lower() in ['true', 'false']:
|
||||
migrated_settings["music_enabled"] = content.lower() == 'true'
|
||||
else:
|
||||
# Fichier JSON normal
|
||||
music_data = json.loads(content)
|
||||
migrated_settings["music_enabled"] = music_data.get("music_enabled", True)
|
||||
files_to_remove.append(MUSIC_CONFIG_PATH)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Migrer accessibility.json
|
||||
if os.path.exists(ACCESSIBILITY_FOLDER):
|
||||
try:
|
||||
with open(ACCESSIBILITY_FOLDER, 'r', encoding='utf-8') as f:
|
||||
acc_data = json.load(f)
|
||||
migrated_settings["accessibility"] = {
|
||||
"font_scale": acc_data.get("font_scale", 1.0)
|
||||
}
|
||||
files_to_remove.append(ACCESSIBILITY_FOLDER)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Migrer symlink_settings.json
|
||||
if os.path.exists(SYMLINK_SETTINGS_PATH):
|
||||
try:
|
||||
with open(SYMLINK_SETTINGS_PATH, 'r', encoding='utf-8') as f:
|
||||
symlink_data = json.load(f)
|
||||
migrated_settings["symlink"] = {
|
||||
"enabled": symlink_data.get("use_symlink_path", False),
|
||||
"target_directory": symlink_data.get("target_directory", "")
|
||||
}
|
||||
files_to_remove.append(SYMLINK_SETTINGS_PATH)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Supprimer les anciens fichiers après migration réussie
|
||||
if files_to_remove:
|
||||
print(f"Migration réussie. Suppression des anciens fichiers de configuration...")
|
||||
for file_path in files_to_remove:
|
||||
try:
|
||||
os.remove(file_path)
|
||||
print(f" - Supprimé: {os.path.basename(file_path)}")
|
||||
except Exception as e:
|
||||
print(f" - Erreur lors de la suppression de {os.path.basename(file_path)}: {e}")
|
||||
|
||||
return migrated_settings
|
||||
|
||||
|
||||
def load_symlink_settings():
|
||||
"""Load symlink settings from rgsx_settings.json."""
|
||||
|
||||
@@ -12,9 +12,9 @@ from rgsx_settings import load_rgsx_settings, save_rgsx_settings
|
||||
import zipfile
|
||||
import time
|
||||
import random
|
||||
from config import JSON_EXTENSIONS, SAVE_FOLDER
|
||||
import config
|
||||
from history import save_history
|
||||
from language import _ # Import de la fonction de traduction
|
||||
from language import _
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@ def detect_non_pc():
|
||||
def load_extensions_json():
|
||||
"""Charge le fichier JSON contenant les extensions supportées."""
|
||||
try:
|
||||
with open(JSON_EXTENSIONS, 'r', encoding='utf-8') as f:
|
||||
with open(config.JSON_EXTENSIONS, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la lecture de {JSON_EXTENSIONS}: {e}")
|
||||
logger.error(f"Erreur lors de la lecture de {config.JSON_EXTENSIONS}: {e}")
|
||||
return []
|
||||
|
||||
def check_extension_before_download(url, platform, game_name):
|
||||
@@ -59,7 +59,7 @@ def check_extension_before_download(url, platform, game_name):
|
||||
sanitized_name = sanitize_filename(game_name)
|
||||
extensions_data = load_extensions_json()
|
||||
if not extensions_data:
|
||||
logger.error(f"Fichier {JSON_EXTENSIONS} vide ou introuvable")
|
||||
logger.error(f"Fichier {config.JSON_EXTENSIONS} vide ou introuvable")
|
||||
return None
|
||||
|
||||
is_supported = is_extension_supported(sanitized_name, platform, extensions_data)
|
||||
@@ -80,13 +80,15 @@ def check_extension_before_download(url, platform, game_name):
|
||||
return None
|
||||
|
||||
# Fonction pour vérifier si l'extension est supportée pour une plateforme donnée
|
||||
def is_extension_supported(filename, platform, extensions_data):
|
||||
"""Vérifie si l'extension du fichier est supportée pour la plateforme donnée."""
|
||||
def is_extension_supported(filename, platform_key, extensions_data):
|
||||
"""Vérifie si l'extension du fichier est supportée pour la plateforme donnée.
|
||||
platform_key correspond maintenant à l'identifiant utilisé dans config.platforms (platform_name)."""
|
||||
extension = os.path.splitext(filename)[1].lower()
|
||||
|
||||
|
||||
dest_dir = None
|
||||
for platform_dict in config.platform_dicts:
|
||||
if platform_dict["platform"] == platform:
|
||||
# Nouveau schéma: platform_name
|
||||
if platform_dict.get("platform_name") == platform_key:
|
||||
dest_dir = os.path.join(config.ROMS_FOLDER, platform_dict.get("folder"))
|
||||
break
|
||||
|
||||
@@ -108,43 +110,225 @@ def is_extension_supported(filename, platform, extensions_data):
|
||||
|
||||
# Fonction pour charger sources.json
|
||||
def load_sources():
|
||||
"""Charge les sources depuis sources.json et initialise les plateformes."""
|
||||
sources_path = os.path.join(config.SOURCES_FILE)
|
||||
logger.debug(f"Chargement de {sources_path}")
|
||||
"""Charge la liste de base depuis systems_list.json puis ajoute les plateformes
|
||||
implicites (fichiers de jeux *.json non listés) à la fin sans réordonner.
|
||||
|
||||
Schéma attendu dans systems_list.json:
|
||||
[ {"platform_name": str, "folder": str, (optionnel) "platform_image"|"system_image": str }, ... ]
|
||||
|
||||
Règles d'ajout automatique:
|
||||
- Chaque fichier <nom>.json dans config.GAMES_FOLDER est candidat.
|
||||
- Si <nom> ne correspond à aucune entrée existante (case sensitive simple),
|
||||
on ajoute {"platform_name": <nom>, "folder": <nom>} en fin de liste.
|
||||
- Aucun tri n'est appliqué pour préserver l'ordre original défini par l'utilisateur.
|
||||
"""
|
||||
try:
|
||||
with open(sources_path, 'r', encoding='utf-8') as f:
|
||||
sources = json.load(f)
|
||||
sources = sorted(sources, key=lambda x: x.get("nom", x.get("platform", "")).lower())
|
||||
config.platforms = [source["platform"] for source in sources]
|
||||
config.platform_dicts = sources
|
||||
config.platform_names = {source["platform"]: source["nom"] for source in sources}
|
||||
config.games_count = {platform: 0 for platform in config.platforms} # Initialiser à 0
|
||||
# Charger les jeux pour chaque plateforme
|
||||
loaded_platforms = set() # Pour suivre les plateformes déjà loguées
|
||||
for platform in config.platforms:
|
||||
games = load_games(platform)
|
||||
config.games_count[platform] = len(games)
|
||||
if platform not in loaded_platforms:
|
||||
loaded_platforms.add(platform)
|
||||
# Appeler write_unavailable_systems une seule fois après la boucle
|
||||
write_unavailable_systems() # Assurez-vous que cette fonction est définie
|
||||
# Détection legacy: si sources.json (ancien format) existe encore, déclencher redownload automatique
|
||||
legacy_path = os.path.join(config.SAVE_FOLDER, "sources.json")
|
||||
if os.path.exists(legacy_path):
|
||||
logger.warning("Ancien fichier sources.json détecté: déclenchement redownload cache jeux")
|
||||
try:
|
||||
# Supprimer ancien cache et forcer redémarrage logique comme dans l'option de menu
|
||||
if os.path.exists(config.SOURCES_FILE):
|
||||
try:
|
||||
os.remove(config.SOURCES_FILE)
|
||||
except Exception:
|
||||
pass
|
||||
if os.path.exists(config.GAMES_FOLDER):
|
||||
shutil.rmtree(config.GAMES_FOLDER, ignore_errors=True)
|
||||
if os.path.exists(config.IMAGES_FOLDER):
|
||||
shutil.rmtree(config.IMAGES_FOLDER, ignore_errors=True)
|
||||
# Renommer legacy pour éviter boucle
|
||||
try:
|
||||
os.replace(legacy_path, legacy_path + ".bak")
|
||||
except Exception:
|
||||
pass
|
||||
# Préparer popup redémarrage si contexte graphique chargé
|
||||
config.popup_message = _("popup_redownload_success") if hasattr(config, 'popup_message') else "Cache jeux réinitialisé"
|
||||
config.popup_timer = 5000 if hasattr(config, 'popup_timer') else 0
|
||||
config.menu_state = "restart_popup" if hasattr(config, 'menu_state') else getattr(config, 'menu_state', 'platform')
|
||||
config.needs_redraw = True
|
||||
logger.info("Redownload cache déclenché automatiquement (legacy sources.json)")
|
||||
return [] # On sort pour laisser le processus de redémarrage gérer le rechargement
|
||||
except Exception as e:
|
||||
logger.error(f"Échec redownload automatique depuis legacy sources.json: {e}")
|
||||
sources = []
|
||||
if os.path.exists(config.SOURCES_FILE):
|
||||
with open(config.SOURCES_FILE, 'r', encoding='utf-8') as f:
|
||||
sources = json.load(f)
|
||||
if not isinstance(sources, list):
|
||||
logger.error("systems_list.json n'est pas une liste JSON valide")
|
||||
sources = []
|
||||
else:
|
||||
logger.warning(f"Fichier systems_list absent: {config.SOURCES_FILE}")
|
||||
|
||||
# S'assurer que chaque entrée possède la clé platform_image (vide si absente)
|
||||
for s in sources:
|
||||
if "platform_image" not in s:
|
||||
# Supporter ancienne clé system_image -> platform_image si présente
|
||||
legacy = s.pop("system_image", "") if isinstance(s, dict) else ""
|
||||
s["platform_image"] = legacy or ""
|
||||
|
||||
existing_names = {s.get("platform_name", "") for s in sources}
|
||||
added = []
|
||||
if os.path.isdir(config.GAMES_FOLDER):
|
||||
for fname in sorted(os.listdir(config.GAMES_FOLDER)):
|
||||
if not fname.lower().endswith('.json'):
|
||||
continue
|
||||
pname = os.path.splitext(fname)[0]
|
||||
if not pname or pname in existing_names:
|
||||
continue
|
||||
new_entry = {"platform_name": pname, "folder": pname, "platform_image": ""}
|
||||
sources.append(new_entry)
|
||||
added.append(pname)
|
||||
existing_names.add(pname)
|
||||
|
||||
# Déterminer les plateformes orphelines (fichier manquant)
|
||||
existing_files = set()
|
||||
if os.path.isdir(config.GAMES_FOLDER):
|
||||
existing_files = {os.path.splitext(f)[0] for f in os.listdir(config.GAMES_FOLDER) if f.lower().endswith('.json')}
|
||||
removed = []
|
||||
filtered_sources = []
|
||||
for entry in sources:
|
||||
pname = entry.get("platform_name", "")
|
||||
# Garder seulement si un fichier existe
|
||||
if pname in existing_files:
|
||||
filtered_sources.append(entry)
|
||||
else:
|
||||
# Ne retirer que si ce n'est pas un nom vide
|
||||
if pname:
|
||||
removed.append(pname)
|
||||
sources = filtered_sources
|
||||
|
||||
if added:
|
||||
logger.info(f"Plateformes ajoutées automatiquement: {', '.join(added)}")
|
||||
if removed:
|
||||
logger.info(f"Plateformes supprimées (fichiers absents): {', '.join(removed)}")
|
||||
|
||||
# Persister si modifications (ajouts ou suppressions)
|
||||
if added or removed:
|
||||
try:
|
||||
# Pas de tri avant persistance: conserver ordre d'origine + ajouts fins
|
||||
os.makedirs(os.path.dirname(config.SOURCES_FILE), exist_ok=True)
|
||||
with open(config.SOURCES_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(sources, f, ensure_ascii=False, indent=2)
|
||||
logger.info("systems_list.json mis à jour (ajouts/suppressions, ordre conservé)")
|
||||
except Exception as e:
|
||||
logger.error(f"Échec écriture systems_list.json après maj auto: {e}")
|
||||
|
||||
# Pour l'affichage on veut un tri alphabétique sans toucher l'ordre de persistance
|
||||
sorted_for_display = sorted(sources, key=lambda x: x.get("platform_name", "").lower())
|
||||
|
||||
# Construire structures config: platform_dicts = ordre fichier, platforms = tri (avec filtre masqués)
|
||||
config.platform_dicts = sources # ordre brut fichier
|
||||
settings = load_rgsx_settings()
|
||||
hidden = set(settings.get("hidden_platforms", [])) if isinstance(settings, dict) else set()
|
||||
all_sorted_names = [s.get("platform_name", "") for s in sorted_for_display]
|
||||
visible_names = [n for n in all_sorted_names if n and n not in hidden]
|
||||
config.platforms = visible_names
|
||||
config.platform_names = {p: p for p in config.platforms}
|
||||
# Nouveau mapping par nom pour éviter décalages index après tri d'affichage
|
||||
try:
|
||||
config.platform_dict_by_name = {d.get("platform_name", ""): d for d in config.platform_dicts}
|
||||
except Exception:
|
||||
config.platform_dict_by_name = {}
|
||||
config.games_count = {}
|
||||
for platform_name in config.platforms:
|
||||
games = load_games(platform_name)
|
||||
config.games_count[platform_name] = len(games)
|
||||
|
||||
write_unavailable_systems()
|
||||
return sources
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du chargement de sources.json : {str(e)}")
|
||||
logger.error(f"Erreur fusion systèmes + détection jeux: {e}")
|
||||
return []
|
||||
|
||||
def load_games(platform_id):
|
||||
"""Charge les jeux pour une plateforme donnée en utilisant platform_id et teste la première URL."""
|
||||
games_path = os.path.join(config.GAMES_FOLDER, f"{platform_id}.json")
|
||||
#logger.debug(f"Chargement des jeux pour {platform_id} depuis {games_path}")
|
||||
"""Charge la liste des jeux pour une plateforme.
|
||||
|
||||
Recherche des fichiers dans config.GAMES_FOLDER selon l'ordre:
|
||||
1. <platform_id>.json (nom exact)
|
||||
2. normalisé: normalize_platform_name(platform_id).json
|
||||
3. <folder>.json (valeur 'folder' du dictionnaire de plateforme)
|
||||
|
||||
Format accepté du fichier JSON:
|
||||
- liste de listes/tuples: [ [name, url], [name, url, size], ... ]
|
||||
- liste de chaînes: [ name1, name2, ... ]
|
||||
- liste de dicts: [ {"game_name"|"name"|"title": str, "url"|"download"|"link"|"href": str?, "size"|"filesize"|"length": str?}, ... ]
|
||||
- dict contenant une clé 'games' avec un des formats ci-dessus
|
||||
|
||||
Retourne une liste normalisée de tuples (name, url_or_None, size_or_None).
|
||||
"""
|
||||
try:
|
||||
with open(games_path, 'r', encoding='utf-8') as f:
|
||||
games = json.load(f)
|
||||
|
||||
logger.debug(f"Jeux chargés pour {platform_id}: {len(games)} jeux")
|
||||
return games
|
||||
# Retrouver l'objet plateforme pour accéder éventuellement à 'folder'
|
||||
platform_dict = None
|
||||
for pd in config.platform_dicts:
|
||||
if pd.get("platform_name") == platform_id or pd.get("platform") == platform_id:
|
||||
platform_dict = pd
|
||||
break
|
||||
|
||||
candidates = []
|
||||
# 1. Nom exact
|
||||
candidates.append(os.path.join(config.GAMES_FOLDER, f"{platform_id}.json"))
|
||||
# 2. Nom normalisé
|
||||
norm = normalize_platform_name(platform_id)
|
||||
if norm and norm != platform_id:
|
||||
candidates.append(os.path.join(config.GAMES_FOLDER, f"{norm}.json"))
|
||||
# 3. Folder déclaré
|
||||
if platform_dict:
|
||||
folder_name = platform_dict.get("folder")
|
||||
if folder_name:
|
||||
candidates.append(os.path.join(config.GAMES_FOLDER, f"{folder_name}.json"))
|
||||
|
||||
game_file = None
|
||||
for c in candidates:
|
||||
if os.path.exists(c):
|
||||
game_file = c
|
||||
break
|
||||
if not game_file:
|
||||
logger.warning(f"Aucun fichier de jeux trouvé pour {platform_id} (candidats: {candidates})")
|
||||
return []
|
||||
|
||||
with open(game_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Si dict avec clé 'games'
|
||||
if isinstance(data, dict) and 'games' in data:
|
||||
data = data['games']
|
||||
|
||||
normalized = [] # (name, url, size)
|
||||
|
||||
def extract_from_dict(d):
|
||||
name = d.get('game_name') or d.get('name') or d.get('title') or d.get('game')
|
||||
url = d.get('url') or d.get('download') or d.get('link') or d.get('href')
|
||||
size = d.get('size') or d.get('filesize') or d.get('length')
|
||||
if name:
|
||||
normalized.append((str(name), url if isinstance(url, str) and url.strip() else None, str(size) if size else None))
|
||||
|
||||
if isinstance(data, list):
|
||||
for item in data:
|
||||
if isinstance(item, (list, tuple)):
|
||||
if len(item) == 0:
|
||||
continue
|
||||
name = str(item[0])
|
||||
url = item[1] if len(item) > 1 and isinstance(item[1], str) and item[1].strip() else None
|
||||
size = item[2] if len(item) > 2 and isinstance(item[2], str) and item[2].strip() else None
|
||||
normalized.append((name, url, size))
|
||||
elif isinstance(item, dict):
|
||||
extract_from_dict(item)
|
||||
elif isinstance(item, str):
|
||||
normalized.append((item, None, None))
|
||||
else:
|
||||
normalized.append((str(item), None, None))
|
||||
elif isinstance(data, dict): # dict sans 'games'
|
||||
extract_from_dict(data)
|
||||
else:
|
||||
logger.warning(f"Format de fichier jeux inattendu pour {platform_id}: {type(data)}")
|
||||
|
||||
logger.debug(f"Jeux chargés pour {platform_id} depuis {os.path.basename(game_file)}: {len(normalized)} entrées")
|
||||
return normalized
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du chargement des jeux pour {platform_id} : {str(e)}")
|
||||
logger.error(f"Erreur lors du chargement des jeux pour {platform_id}: {e}")
|
||||
return []
|
||||
|
||||
def write_unavailable_systems():
|
||||
@@ -156,12 +340,11 @@ def write_unavailable_systems():
|
||||
# Formater la date et l'heure pour le nom du fichier
|
||||
current_time = datetime.now()
|
||||
timestamp = current_time.strftime("%d-%m-%Y-%H-%M")
|
||||
log_dir = os.path.join(os.path.dirname(config.APP_FOLDER), "logs", "RGSX")
|
||||
log_file = os.path.join(log_dir, f"systemes_unavailable_{timestamp}.txt")
|
||||
log_file = os.path.join(config.log_dir, f"systemes_unavailable_{timestamp}.txt")
|
||||
|
||||
try:
|
||||
# Créer le répertoire s'il n'existe pas
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
os.makedirs(config.log_dir, exist_ok=True)
|
||||
|
||||
# Écrire les systèmes dans le fichier
|
||||
with open(log_file, 'w', encoding='utf-8') as f:
|
||||
@@ -290,15 +473,28 @@ def wrap_text(text, font, max_width):
|
||||
return lines
|
||||
|
||||
def load_system_image(platform_dict):
|
||||
"""Charge une image système depuis le chemin spécifié dans system_image."""
|
||||
image_path = os.path.join(config.IMAGES_FOLDER, platform_dict.get("system_image", "default.png"))
|
||||
platform_name = platform_dict.get("platform", "unknown")
|
||||
#logger.debug(f"Chargement de l'image système pour {platform_name} depuis {image_path}")
|
||||
"""Charge une image système avec priorité:
|
||||
1. Fichier nommé exactement <platform_name>.png
|
||||
2. Champ platform_image si non vide
|
||||
3. Fallback default.png"""
|
||||
platform_name = platform_dict.get("platform_name", "unknown")
|
||||
preferred_filename = f"{platform_name}.png"
|
||||
preferred_path = os.path.join(config.IMAGES_FOLDER, preferred_filename)
|
||||
|
||||
# Normaliser platform_image pouvant être vide
|
||||
platform_image_field = platform_dict.get("platform_image") or ""
|
||||
explicit_image_path = os.path.join(config.IMAGES_FOLDER, platform_image_field) if platform_image_field else None
|
||||
default_path = os.path.join(config.IMAGES_FOLDER, "default.png")
|
||||
|
||||
try:
|
||||
if not os.path.exists(image_path):
|
||||
logger.error(f"Image introuvable pour {platform_name} à {image_path}")
|
||||
return None
|
||||
return pygame.image.load(image_path).convert_alpha()
|
||||
if os.path.exists(preferred_path):
|
||||
return pygame.image.load(preferred_path).convert_alpha()
|
||||
if explicit_image_path and os.path.exists(explicit_image_path):
|
||||
return pygame.image.load(explicit_image_path).convert_alpha()
|
||||
if os.path.exists(default_path):
|
||||
return pygame.image.load(default_path).convert_alpha()
|
||||
logger.error(f"Aucune image trouvée pour {platform_name} (cherché: {preferred_path}, {explicit_image_path}, default.png)")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du chargement de l'image pour {platform_name} : {str(e)}")
|
||||
return None
|
||||
@@ -447,16 +643,6 @@ def extract_rar(rar_path, dest_dir, url):
|
||||
if system_type == "Windows":
|
||||
# Sur Windows, utiliser directement config.UNRAR_EXE
|
||||
unrar_exe = config.UNRAR_EXE
|
||||
if not os.path.exists(unrar_exe):
|
||||
logger.warning("unrar.exe absent, téléchargement en cours...")
|
||||
try:
|
||||
import urllib.request
|
||||
os.makedirs(os.path.dirname(unrar_exe), exist_ok=True)
|
||||
urllib.request.urlretrieve(config.unrar_download_exe, unrar_exe)
|
||||
logger.info(f"unrar.exe téléchargé dans {unrar_exe}")
|
||||
except Exception as e:
|
||||
logger.error(f"Impossible de télécharger unrar.exe: {str(e)}")
|
||||
return False, _("utils_unrar_unavailable")
|
||||
unrar_cmd = [unrar_exe]
|
||||
else:
|
||||
# Linux/Batocera: utiliser 'unrar' du système
|
||||
@@ -657,32 +843,11 @@ def handle_xbox(dest_dir, iso_files):
|
||||
if system_type == "Windows":
|
||||
# Sur Windows; telecharger le fichier exe
|
||||
XDVDFS_EXE = config.XDVDFS_EXE
|
||||
if not os.path.exists(XDVDFS_EXE):
|
||||
logger.warning("xdvdfs.exe absent, téléchargement en cours...")
|
||||
try:
|
||||
import urllib.request
|
||||
os.makedirs(os.path.dirname(XDVDFS_EXE), exist_ok=True)
|
||||
urllib.request.urlretrieve(config.xdvdfs_download_exe, XDVDFS_EXE)
|
||||
logger.info(f"xdvdfs.exe téléchargé dans {XDVDFS_EXE}")
|
||||
except Exception as e:
|
||||
logger.error(f"Impossible de télécharger xdvdfs.exe: {str(e)}")
|
||||
return False, _("utils_xdvdfs_unavailable")
|
||||
xdvdfs_cmd = [XDVDFS_EXE, "pack"] # Liste avec 2 éléments
|
||||
|
||||
else:
|
||||
# Linux/Batocera : télécharger le fichier xdvdfs
|
||||
XDVDFS_LINUX = config.XDVDFS_LINUX
|
||||
if not os.path.exists(XDVDFS_LINUX):
|
||||
logger.warning("xdvdfs non trouvé, téléchargement en cours...")
|
||||
try:
|
||||
import urllib.request
|
||||
os.makedirs(os.path.dirname(XDVDFS_LINUX), exist_ok=True)
|
||||
urllib.request.urlretrieve(config.xdvdfs_download_linux, XDVDFS_LINUX)
|
||||
os.chmod(XDVDFS_LINUX, 0o755) # Rendre exécutable
|
||||
logger.info(f"xdvdfs téléchargé dans {XDVDFS_LINUX}")
|
||||
except Exception as e:
|
||||
logger.error(f"Impossible de télécharger xdvdfs: {str(e)}")
|
||||
return False, _("utils_xdvdfs_unavailable") # Vérifier les permissions après le téléchargement
|
||||
try:
|
||||
stat_info = os.stat(XDVDFS_LINUX)
|
||||
mode = stat_info.st_mode
|
||||
@@ -792,24 +957,24 @@ def set_music_popup(music_name):
|
||||
|
||||
def load_api_key_1fichier():
|
||||
"""Charge la clé API 1fichier depuis le dossier de sauvegarde, crée le fichier si absent."""
|
||||
api_path = os.path.join(SAVE_FOLDER, "1fichierAPI.txt")
|
||||
logger.debug(f"Tentative de chargement de la clé API depuis: {api_path}")
|
||||
|
||||
logger.debug(f"Tentative de chargement de la clé API depuis: {config.API_KEY_1FICHIER}")
|
||||
try:
|
||||
# Vérifie si le fichier existe déjà
|
||||
if not os.path.exists(api_path):
|
||||
if not os.path.exists(config.API_KEY_1FICHIER):
|
||||
# Crée le dossier parent si nécessaire
|
||||
os.makedirs(SAVE_FOLDER, exist_ok=True)
|
||||
os.makedirs(config.SAVE_FOLDER, exist_ok=True)
|
||||
# Crée le fichier vide si absent
|
||||
with open(api_path, "w") as f:
|
||||
with open(config.API_KEY_1FICHIER, "w") as f:
|
||||
f.write("")
|
||||
logger.info(f"Fichier de clé API créé : {api_path}")
|
||||
logger.info(f"Fichier de clé API créé : {config.API_KEY_1FICHIER}")
|
||||
return ""
|
||||
except OSError as e:
|
||||
logger.error(f"Erreur lors de la création du fichier de clé API : {e}")
|
||||
return ""
|
||||
# Lit la clé API depuis le fichier
|
||||
try:
|
||||
with open(api_path, "r", encoding="utf-8") as f:
|
||||
with open(config.API_KEY_1FICHIER, "r", encoding="utf-8") as f:
|
||||
api_key = f.read().strip()
|
||||
logger.debug(f"Clé API 1fichier lue: '{api_key}' (longueur: {len(api_key)})")
|
||||
if not api_key:
|
||||
|
||||
Reference in New Issue
Block a user