Initial commit with RGSX project files
This commit is contained in:
381
network.py
Normal file
381
network.py
Normal file
@@ -0,0 +1,381 @@
|
||||
import requests
|
||||
import subprocess
|
||||
import re
|
||||
import os
|
||||
import threading
|
||||
import pygame
|
||||
import zipfile
|
||||
import json
|
||||
from urllib.parse import urljoin, unquote
|
||||
import asyncio
|
||||
import config
|
||||
from utils import sanitize_filename
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
JSON_EXTENSIONS = "/userdata/roms/ports/RGSX/rom_extensions.json"
|
||||
|
||||
def test_internet():
|
||||
logger.debug("Test de connexion Internet")
|
||||
try:
|
||||
result = subprocess.run(['ping', '-c', '4', '8.8.8.8'], capture_output=True, text=True, timeout=5)
|
||||
if result.returncode == 0:
|
||||
logger.debug("Connexion Internet OK")
|
||||
return True
|
||||
else:
|
||||
logger.debug("Échec ping 8.8.8.8")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.debug(f"Erreur test Internet: {str(e)}")
|
||||
return False
|
||||
|
||||
def load_extensions_json():
|
||||
"""Charge le fichier JSON contenant les extensions supportées."""
|
||||
try:
|
||||
with open(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}")
|
||||
return []
|
||||
|
||||
def is_extension_supported(filename, platform, extensions_data):
|
||||
"""Vérifie si l'extension du fichier est supportée pour la plateforme donnée."""
|
||||
extension = os.path.splitext(filename)[1].lower()
|
||||
dest_dir = None
|
||||
for platform_dict in config.platform_dicts:
|
||||
if platform_dict["platform"] == platform:
|
||||
dest_dir = platform_dict.get("folder")
|
||||
break
|
||||
if not dest_dir:
|
||||
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
|
||||
dest_dir = os.path.join("/userdata/roms", platform)
|
||||
for system in extensions_data:
|
||||
if system["folder"] == dest_dir:
|
||||
return extension in system["extensions"]
|
||||
logger.warning(f"Aucun système trouvé pour le dossier {dest_dir}")
|
||||
return False
|
||||
|
||||
def extract_zip(zip_path, dest_dir, url):
|
||||
"""Extrait le contenu du fichier ZIP dans le dossier cible avec un suivi progressif de la progression."""
|
||||
try:
|
||||
lock = threading.Lock()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
total_size = sum(info.file_size for info in zip_ref.infolist() if not info.is_dir())
|
||||
logger.info(f"Taille totale à extraire: {total_size} octets")
|
||||
if total_size == 0:
|
||||
logger.warning("ZIP vide ou ne contenant que des dossiers")
|
||||
return True, "ZIP vide extrait avec succès"
|
||||
|
||||
extracted_size = 0
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
chunk_size = 8192
|
||||
for info in zip_ref.infolist():
|
||||
if info.is_dir():
|
||||
continue
|
||||
file_path = os.path.join(dest_dir, info.filename)
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with zip_ref.open(info) as source, open(file_path, 'wb') as dest:
|
||||
file_size = info.file_size
|
||||
file_extracted = 0
|
||||
while True:
|
||||
chunk = source.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
dest.write(chunk)
|
||||
file_extracted += len(chunk)
|
||||
extracted_size += len(chunk)
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = extracted_size
|
||||
config.download_progress[url]["total_size"] = total_size
|
||||
config.download_progress[url]["status"] = "Extracting"
|
||||
config.download_progress[url]["progress_percent"] = (extracted_size / total_size * 100) if total_size > 0 else 0
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
logger.debug(f"Extraction {info.filename}, chunk: {len(chunk)}, file_extracted: {file_extracted}/{file_size}, total_extracted: {extracted_size}/{total_size}, progression: {(extracted_size/total_size*100):.1f}%")
|
||||
os.chmod(file_path, 0o644)
|
||||
|
||||
for root, dirs, _ in os.walk(dest_dir):
|
||||
for dir_name in dirs:
|
||||
os.chmod(os.path.join(root, dir_name), 0o755)
|
||||
|
||||
os.remove(zip_path)
|
||||
logger.info(f"Fichier ZIP {zip_path} extrait dans {dest_dir} et supprimé")
|
||||
return True, "ZIP extrait avec succès"
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'extraction de {zip_path}: {e}")
|
||||
return False, str(e)
|
||||
|
||||
def extract_rar(rar_path, dest_dir, url):
|
||||
"""Extrait le contenu du fichier RAR dans le dossier cible, préservant la structure des dossiers."""
|
||||
try:
|
||||
lock = threading.Lock()
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
|
||||
result = subprocess.run(['unrar'], capture_output=True, text=True)
|
||||
if result.returncode not in [0, 1]:
|
||||
logger.error("Commande unrar non disponible")
|
||||
return False, "Commande unrar non disponible"
|
||||
|
||||
result = subprocess.run(['unrar', 'l', '-v', rar_path], capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr.strip()
|
||||
logger.error(f"Erreur lors de la liste des fichiers RAR: {error_msg}")
|
||||
return False, f"Échec de la liste des fichiers RAR: {error_msg}"
|
||||
|
||||
logger.debug(f"Sortie brute de 'unrar l -v {rar_path}':\n{result.stdout}")
|
||||
|
||||
total_size = 0
|
||||
files_to_extract = []
|
||||
root_dirs = set()
|
||||
lines = result.stdout.splitlines()
|
||||
in_file_list = False
|
||||
for line in lines:
|
||||
if line.startswith("----"):
|
||||
in_file_list = not in_file_list
|
||||
continue
|
||||
if in_file_list:
|
||||
match = re.match(r'^\s*(\S+)\s+(\d+)\s+\d*\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})\s+(.+)$', line)
|
||||
if match:
|
||||
attrs = match.group(1)
|
||||
file_size = int(match.group(2))
|
||||
file_date = match.group(3)
|
||||
file_name = match.group(4).strip()
|
||||
if 'D' not in attrs:
|
||||
files_to_extract.append((file_name, file_size))
|
||||
total_size += file_size
|
||||
root_dir = file_name.split('/')[0] if '/' in file_name else ''
|
||||
if root_dir:
|
||||
root_dirs.add(root_dir)
|
||||
logger.debug(f"Ligne parsée: {file_name}, taille: {file_size}, date: {file_date}")
|
||||
else:
|
||||
logger.debug(f"Dossier ignoré: {file_name}")
|
||||
else:
|
||||
logger.debug(f"Ligne ignorée (format inattendu): {line}")
|
||||
|
||||
logger.info(f"Taille totale à extraire (RAR): {total_size} octets")
|
||||
logger.debug(f"Fichiers à extraire: {files_to_extract}")
|
||||
logger.debug(f"Dossiers racines détectés: {root_dirs}")
|
||||
if total_size == 0:
|
||||
logger.warning("RAR vide, ne contenant que des dossiers, ou erreur de parsing")
|
||||
return False, "RAR vide ou erreur lors de la liste des fichiers"
|
||||
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = 0
|
||||
config.download_progress[url]["total_size"] = total_size
|
||||
config.download_progress[url]["status"] = "Extracting"
|
||||
config.download_progress[url]["progress_percent"] = 0
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
|
||||
escaped_rar_path = rar_path.replace(" ", "\\ ")
|
||||
escaped_dest_dir = dest_dir.replace(" ", "\\ ")
|
||||
process = subprocess.Popen(['unrar', 'x', '-y', escaped_rar_path, escaped_dest_dir],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
if process.returncode != 0:
|
||||
logger.error(f"Erreur lors de l'extraction de {rar_path}: {stderr}")
|
||||
return False, f"Erreur lors de l'extraction: {stderr}"
|
||||
|
||||
extracted_size = 0
|
||||
extracted_files = []
|
||||
for root, _, files in os.walk(dest_dir):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, dest_dir).replace(os.sep, '/')
|
||||
for expected_file, file_size in files_to_extract:
|
||||
if rel_path == expected_file:
|
||||
extracted_size += file_size
|
||||
extracted_files.append(expected_file)
|
||||
os.chmod(file_path, 0o644)
|
||||
logger.debug(f"Fichier extrait: {expected_file}, taille: {file_size}, chemin: {file_path}")
|
||||
break
|
||||
|
||||
missing_files = [f for f, _ in files_to_extract if f not in extracted_files]
|
||||
if missing_files:
|
||||
logger.warning(f"Fichiers non extraits: {', '.join(missing_files)}")
|
||||
return False, f"Fichiers non extraits: {', '.join(missing_files)}"
|
||||
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = extracted_size
|
||||
config.download_progress[url]["total_size"] = total_size
|
||||
config.download_progress[url]["status"] = "Extracting"
|
||||
config.download_progress[url]["progress_percent"] = 100 if total_size > 0 else 0
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
|
||||
if dest_dir == "/userdata/roms/ps3" and len(root_dirs) == 1:
|
||||
root_dir = root_dirs.pop()
|
||||
old_path = os.path.join(dest_dir, root_dir)
|
||||
new_path = os.path.join(dest_dir, f"{root_dir}.ps3")
|
||||
if os.path.isdir(old_path):
|
||||
try:
|
||||
os.rename(old_path, new_path)
|
||||
logger.info(f"Dossier renommé: {old_path} -> {new_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du renommage de {old_path} en {new_path}: {str(e)}")
|
||||
return False, f"Erreur lors du renommage du dossier: {str(e)}"
|
||||
else:
|
||||
logger.warning(f"Dossier racine {old_path} non trouvé après extraction")
|
||||
elif dest_dir == "/userdata/roms/ps3" and len(root_dirs) > 1:
|
||||
logger.warning(f"Plusieurs dossiers racines détectés dans l'archive: {root_dirs}. Aucun renommage effectué.")
|
||||
|
||||
for root, dirs, _ in os.walk(dest_dir):
|
||||
for dir_name in dirs:
|
||||
os.chmod(os.path.join(root, dir_name), 0o755)
|
||||
|
||||
os.remove(rar_path)
|
||||
logger.info(f"Fichier RAR {rar_path} extrait dans {dest_dir} et supprimé")
|
||||
return True, "RAR extrait avec succès"
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'extraction de {rar_path}: {str(e)}")
|
||||
return False, str(e)
|
||||
finally:
|
||||
if os.path.exists(rar_path):
|
||||
try:
|
||||
os.remove(rar_path)
|
||||
logger.info(f"Fichier RAR {rar_path} supprimé après échec de l'extraction")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la suppression de {rar_path}: {str(e)}")
|
||||
|
||||
async def download_rom(url, platform, game_name, is_zip_non_supported=False):
|
||||
logger.debug(f"Début téléchargement: {game_name} depuis {url}, is_zip_non_supported={is_zip_non_supported}")
|
||||
result = [None, None]
|
||||
|
||||
def download_thread():
|
||||
logger.debug(f"Thread téléchargement démarré pour {url}")
|
||||
try:
|
||||
dest_dir = None
|
||||
for platform_dict in config.platform_dicts:
|
||||
if platform_dict["platform"] == platform:
|
||||
dest_dir = platform_dict.get("folder")
|
||||
break
|
||||
if not dest_dir:
|
||||
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
|
||||
dest_dir = os.path.join("/userdata/roms", platform)
|
||||
|
||||
logger.debug(f"Vérification répertoire destination: {dest_dir}")
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
if not os.access(dest_dir, os.W_OK):
|
||||
raise PermissionError(f"Pas de permission d'écriture dans {dest_dir}")
|
||||
|
||||
sanitized_name = sanitize_filename(game_name)
|
||||
dest_path = os.path.join(dest_dir, f"{sanitized_name}")
|
||||
logger.debug(f"Chemin destination: {dest_path}")
|
||||
|
||||
lock = threading.Lock()
|
||||
with lock:
|
||||
config.download_progress[url] = {
|
||||
"downloaded_size": 0,
|
||||
"total_size": 0,
|
||||
"status": "Téléchargement",
|
||||
"progress_percent": 0,
|
||||
"game_name": game_name
|
||||
}
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
logger.debug(f"Progression initialisée pour {url}")
|
||||
|
||||
headers = {'User-Agent': 'Mozilla/5.0'}
|
||||
logger.debug(f"Envoi requête GET à {url}")
|
||||
response = requests.get(url, stream=True, headers=headers, timeout=30)
|
||||
logger.debug(f"Réponse reçue, status: {response.status_code}")
|
||||
response.raise_for_status()
|
||||
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
logger.debug(f"Taille totale: {total_size} octets")
|
||||
with lock:
|
||||
config.download_progress[url]["total_size"] = total_size
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
|
||||
downloaded = 0
|
||||
with open(dest_path, 'wb') as f:
|
||||
logger.debug(f"Ouverture fichier: {dest_path}")
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = downloaded
|
||||
config.download_progress[url]["status"] = "Téléchargement"
|
||||
config.download_progress[url]["progress_percent"] = (downloaded / total_size * 100) if total_size > 0 else 0
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
logger.debug(f"Progression: {downloaded}/{total_size} octets, {config.download_progress[url]['progress_percent']:.1f}%")
|
||||
|
||||
if is_zip_non_supported:
|
||||
with lock:
|
||||
config.download_progress[url]["downloaded_size"] = 0
|
||||
config.download_progress[url]["total_size"] = 0
|
||||
config.download_progress[url]["status"] = "Extracting"
|
||||
config.download_progress[url]["progress_percent"] = 0
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
extension = os.path.splitext(dest_path)[1].lower()
|
||||
if extension == ".zip":
|
||||
success, msg = extract_zip(dest_path, dest_dir, url)
|
||||
elif extension == ".rar":
|
||||
success, msg = extract_rar(dest_path, dest_dir, url)
|
||||
else:
|
||||
raise Exception(f"Type d'archive non supporté: {extension}")
|
||||
if not success:
|
||||
raise Exception(f"Échec de l'extraction de l'archive: {msg}")
|
||||
result[0] = True
|
||||
result[1] = f"Téléchargé et extrait : {game_name}"
|
||||
else:
|
||||
os.chmod(dest_path, 0o644)
|
||||
logger.debug(f"Téléchargement terminé: {dest_path}")
|
||||
result[0] = True
|
||||
result[1] = f"Téléchargé : {game_name}"
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur téléchargement {url}: {str(e)}")
|
||||
if url in config.download_progress:
|
||||
with lock:
|
||||
del config.download_progress[url]
|
||||
if os.path.exists(dest_path):
|
||||
os.remove(dest_path)
|
||||
result[0] = False
|
||||
result[1] = str(e)
|
||||
finally:
|
||||
logger.debug(f"Thread téléchargement terminé pour {url}")
|
||||
with lock:
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
|
||||
thread = threading.Thread(target=download_thread)
|
||||
logger.debug(f"Démarrage thread pour {url}")
|
||||
thread.start()
|
||||
while thread.is_alive():
|
||||
pygame.event.pump()
|
||||
await asyncio.sleep(0.1)
|
||||
thread.join()
|
||||
logger.debug(f"Thread rejoint pour {url}")
|
||||
|
||||
with threading.Lock():
|
||||
config.download_result_message = result[1]
|
||||
config.download_result_error = not result[0]
|
||||
config.download_result_start_time = pygame.time.get_ticks()
|
||||
config.menu_state = "download_result"
|
||||
config.needs_redraw = True # Forcer le redraw
|
||||
logger.debug(f"Transition vers download_result, message={result[1]}, erreur={not result[0]}")
|
||||
return result[0], result[1]
|
||||
|
||||
def check_extension_before_download(url, platform, game_name):
|
||||
"""Vérifie l'extension avant de lancer le téléchargement."""
|
||||
try:
|
||||
sanitized_name = sanitize_filename(game_name)
|
||||
extensions_data = load_extensions_json()
|
||||
if not extensions_data:
|
||||
logger.error(f"Fichier {JSON_EXTENSIONS} vide ou introuvable")
|
||||
return False, "Fichier de configuration des extensions introuvable", False
|
||||
|
||||
is_supported = is_extension_supported(sanitized_name, platform, extensions_data)
|
||||
extension = os.path.splitext(sanitized_name)[1].lower()
|
||||
is_archive = extension in (".zip", ".rar")
|
||||
|
||||
if is_supported:
|
||||
logger.debug(f"L'extension de {sanitized_name} est supportée pour {platform}")
|
||||
return True, "", False
|
||||
else:
|
||||
if is_archive:
|
||||
logger.debug(f"Fichier {extension.upper()} détecté pour {sanitized_name}, extraction automatique prévue")
|
||||
return False, f"Fichiers {extension.upper()} non supportés par cette plateforme, extraction automatique après le téléchargement.", True
|
||||
logger.debug(f"L'extension de {sanitized_name} n'est pas supportée pour {platform}")
|
||||
return False, f"L'extension de {sanitized_name} n'est pas supportée pour {platform}", False
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur vérification extension {url}: {str(e)}")
|
||||
return False, str(e), False
|
||||
Reference in New Issue
Block a user