import subprocess import logging from dataclasses import dataclass, field from pathlib import Path from typing import Optional logger = logging.getLogger(__name__) PACMAN_LOCK = Path("/var/lib/pacman/db.lck") @dataclass class Package: name: str old_version: str new_version: str @dataclass class InstallResult: success: bool installed: list = field(default_factory=list) failed: list = field(default_factory=list) error: Optional[str] = None def is_pacman_locked() -> bool: return PACMAN_LOCK.exists() def check_updates() -> list[Package]: """Vérifie les mises à jour disponibles via checkupdates (sans droits root).""" result = subprocess.run( ["checkupdates"], capture_output=True, text=True, timeout=120 ) packages = [] for line in result.stdout.strip().splitlines(): parts = line.split() # Format attendu : "nom ancienne_version -> nouvelle_version" if len(parts) >= 4 and parts[2] == "->": packages.append(Package( name=parts[0], old_version=parts[1], new_version=parts[3], )) return packages def download_packages(names: list[str]) -> bool: """Télécharge les paquets dans le cache sans les installer.""" if not names: return True result = subprocess.run( ["pacman", "-Syw", "--noconfirm", "--noprogressbar"] + names, capture_output=True, text=True, timeout=600 ) if result.returncode != 0: logger.error(f"Téléchargement échoué : {result.stderr[-500:]}") return False return True def sync_db() -> bool: """Synchronise la base de données des paquets.""" result = subprocess.run( ["pacman", "-Sy", "--noconfirm"], capture_output=True, text=True, timeout=120 ) return result.returncode == 0 def install_packages(names: list[str]) -> InstallResult: """Installe des paquets spécifiques depuis le cache.""" if not names: return InstallResult(success=True) result = subprocess.run( ["pacman", "-S", "--noconfirm", "--needed", "--noprogressbar"] + names, capture_output=True, text=True, timeout=600 ) if result.returncode == 0: return InstallResult(success=True, installed=names) else: return InstallResult( success=False, failed=names, error=result.stderr[-800:], ) def upgrade_cached_packages(expected_names: list[str]) -> InstallResult: """Installe toutes les mises à jour présentes dans le cache pacman. Utilisé à l'extinction pour éviter les échecs de dépendances liés aux mises à jour partielles : pacman -Su installe l'ensemble cohérent des paquets déjà téléchargés plutôt qu'une liste isolée. """ if not expected_names: return InstallResult(success=True) result = subprocess.run( ["pacman", "-Su", "--noconfirm", "--noprogressbar"], capture_output=True, text=True, timeout=600 ) if result.returncode == 0: return InstallResult(success=True, installed=expected_names) else: return InstallResult( success=False, failed=expected_names, error=result.stderr[-800:], )