Skip to content

Commit 4d914d2

Browse files
committed
Ajouter l'annulation utilisateur des opérations longues du VenvManager
1 parent c6efb29 commit 4d914d2

1 file changed

Lines changed: 110 additions & 7 deletions

File tree

Core/Venv_Manager/Manager.py

Lines changed: 110 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ def __init__(self, parent_widget):
7979
self._auto_venv_cache: dict[str, str] = {}
8080
# Cache for manager-provided venv per workspace (None if not found)
8181
self._manager_venv_cache: dict[str, str | None] = {}
82+
# User-driven cancellation flag for long-running async flows
83+
self._cancel_requested = False
8284

8385
# ---------- Workspace pref (.ark/pref.json) ----------
8486
def _workspace_pref_path(self, workspace_dir: str) -> str:
@@ -561,13 +563,17 @@ def _done(code, _status):
561563
def ensure_tools_installed(self, venv_root: str, tools: list[str]) -> None:
562564
"""Asynchronously check/install the provided tools list with progress dialog."""
563565
try:
566+
self._reset_cancel_state()
564567
self._venv_check_pkgs = list(tools)
565568
self._venv_check_index = 0
566569
self._venv_check_pip_exe = self.pip_path(venv_root)
567570
self._venv_check_path = venv_root
568571
self._venv_check_use_python = False
569572
self.venv_check_progress = ProgressDialog(
570-
"Vérification du venv", self.parent
573+
"Vérification du venv", self.parent, cancelable=True
574+
)
575+
self._bind_cancel_for_dialog(
576+
self.venv_check_progress, "vérification des outils du venv"
571577
)
572578
self.venv_check_progress.set_message(f"Vérification de {tools[0]}...")
573579
self.venv_check_progress.set_progress(0, len(tools))
@@ -581,6 +587,7 @@ def ensure_tools_installed(self, venv_root: str, tools: list[str]) -> None:
581587
def ensure_tools_installed_system(self, tools: list[str]) -> None:
582588
"""Asynchronously check/install tools in system Python using pip."""
583589
try:
590+
self._reset_cancel_state()
584591
self._venv_check_pkgs = list(tools)
585592
self._venv_check_index = 0
586593
self._venv_check_pip_exe = sys.executable
@@ -589,7 +596,10 @@ def ensure_tools_installed_system(self, tools: list[str]) -> None:
589596
)
590597
self._venv_check_use_python = True
591598
self.venv_check_progress = ProgressDialog(
592-
"Vérification du Python système", self.parent
599+
"Vérification du Python système", self.parent, cancelable=True
600+
)
601+
self._bind_cancel_for_dialog(
602+
self.venv_check_progress, "vérification des outils système"
593603
)
594604
self.venv_check_progress.set_message(f"Vérification de {tools[0]}...")
595605
self.venv_check_progress.set_progress(0, len(tools))
@@ -698,6 +708,41 @@ def _safe_log(
698708
except Exception:
699709
pass
700710

711+
def _reset_cancel_state(self) -> None:
712+
try:
713+
self._cancel_requested = False
714+
except Exception:
715+
pass
716+
717+
def _is_cancel_requested(self) -> bool:
718+
try:
719+
return bool(getattr(self, "_cancel_requested", False))
720+
except Exception:
721+
return False
722+
723+
def _request_cancel(self, action_label: str | None = None) -> None:
724+
if self._is_cancel_requested():
725+
return
726+
self._cancel_requested = True
727+
suffix = f" ({action_label})" if action_label else ""
728+
self._safe_log(
729+
f"🛑 Annulation demandée par l'utilisateur{suffix}.",
730+
f"🛑 Cancellation requested by user{suffix}.",
731+
level="warning",
732+
)
733+
self.terminate_tasks()
734+
735+
def _bind_cancel_for_dialog(self, dialog, action_label: str) -> None:
736+
if dialog is None:
737+
return
738+
btn = getattr(dialog, "btn_cancel", None)
739+
if btn is None:
740+
return
741+
try:
742+
btn.clicked.connect(lambda: self._request_cancel(action_label))
743+
except Exception:
744+
pass
745+
701746
def _is_stdlib_module(self, module_name: str) -> bool:
702747
"""Check if a module is part of Python's standard library."""
703748
try:
@@ -1309,6 +1354,7 @@ def _t(_key: str, fr: str, en: str) -> str:
13091354
# ---------- Existing venv: check and install tools ----------
13101355
def check_tools_in_venv(self, venv_path: str):
13111356
try:
1357+
self._reset_cancel_state()
13121358
ok, reason = self.validate_venv_strict(venv_path)
13131359
if not ok:
13141360
self._safe_log(f"❌ Invalid venv: {reason}")
@@ -1336,7 +1382,10 @@ def _after_binding(ok_bind: bool):
13361382
self._venv_check_pip_exe = pip_exe
13371383
self._venv_check_path = venv_path
13381384
self.venv_check_progress = ProgressDialog(
1339-
"Vérification du venv", self.parent
1385+
"Vérification du venv", self.parent, cancelable=True
1386+
)
1387+
self._bind_cancel_for_dialog(
1388+
self.venv_check_progress, "vérification des outils du venv"
13401389
)
13411390
self.venv_check_progress.set_message("Vérification de PyInstaller...")
13421391
self.venv_check_progress.set_progress(0, len(self._venv_check_pkgs))
@@ -1348,6 +1397,13 @@ def _after_binding(ok_bind: bool):
13481397
self._safe_log(f"❌ Erreur lors de la vérification du venv: {e}")
13491398

13501399
def _check_next_venv_pkg(self):
1400+
if self._is_cancel_requested():
1401+
try:
1402+
if self.venv_check_progress:
1403+
self.venv_check_progress.close()
1404+
except Exception:
1405+
pass
1406+
return
13511407
if self._venv_check_index >= len(self._venv_check_pkgs):
13521408
try:
13531409
self.venv_check_progress.set_message("Vérification terminée.")
@@ -1386,6 +1442,8 @@ def _check_next_venv_pkg(self):
13861442
def _on_venv_pkg_checked(self, process, code, status, pkg):
13871443
if getattr(self.parent, "_closing", False):
13881444
return
1445+
if self._is_cancel_requested():
1446+
return
13891447
if code == 0:
13901448
if self._venv_check_use_python:
13911449
self._safe_log(
@@ -1448,6 +1506,8 @@ def _on_venv_pkg_checked(self, process, code, status, pkg):
14481506
def _on_venv_check_output(self, process, error=False):
14491507
if getattr(self.parent, "_closing", False):
14501508
return
1509+
if self._is_cancel_requested():
1510+
return
14511511
data = (
14521512
process.readAllStandardError().data().decode()
14531513
if error
@@ -1743,6 +1803,8 @@ def select_best_venv(self, workspace_dir: str) -> str | None:
17431803
def _on_venv_pkg_installed(self, process, code, status, pkg):
17441804
if getattr(self.parent, "_closing", False):
17451805
return
1806+
if self._is_cancel_requested():
1807+
return
17461808
if code == 0:
17471809
self._safe_log(f"✅ {pkg} installé dans le venv.")
17481810
else:
@@ -1795,6 +1857,7 @@ def create_venv_if_needed(self, path: str, prefer_manager: bool = True):
17951857

17961858
self._safe_log("🔧 Aucun venv trouvé, création automatique...")
17971859
try:
1860+
self._reset_cancel_state()
17981861
# Recherche d'un python embarqué à côté de l'exécutable
17991862
python_candidate = None
18001863
exe_dir = os.path.dirname(sys.executable)
@@ -1846,7 +1909,10 @@ def create_venv_if_needed(self, path: str, prefer_manager: bool = True):
18461909
self._safe_log(f"➡️ Utilisation de sys.executable : {python_candidate}")
18471910

18481911
self.venv_progress_dialog = ProgressDialog(
1849-
"Création de l'environnement virtuel", self.parent
1912+
"Création de l'environnement virtuel", self.parent, cancelable=True
1913+
)
1914+
self._bind_cancel_for_dialog(
1915+
self.venv_progress_dialog, "création de l'environnement virtuel"
18501916
)
18511917
self.venv_progress_dialog.set_message("Création du venv...")
18521918

@@ -1883,6 +1949,8 @@ def create_venv_if_needed(self, path: str, prefer_manager: bool = True):
18831949
def _on_venv_output(self, process, error=False):
18841950
if getattr(self.parent, "_closing", False):
18851951
return
1952+
if self._is_cancel_requested():
1953+
return
18861954
data = (
18871955
process.readAllStandardError().data().decode()
18881956
if error
@@ -1902,6 +1970,9 @@ def _on_venv_output(self, process, error=False):
19021970
def _on_venv_created(self, process, code, status, venv_path):
19031971
if getattr(self.parent, "_closing", False):
19041972
return
1973+
if self._is_cancel_requested():
1974+
self._safe_log("ℹ️ Création du venv annulée.")
1975+
return
19051976
if code == 0:
19061977
self._safe_log("✅ Environnement virtuel créé avec succès.")
19071978
try:
@@ -2370,6 +2441,7 @@ def _start_requirements_install(
23702441
req_path: str,
23712442
use_system_python: bool = False,
23722443
):
2444+
self._reset_cancel_state()
23732445
py_exe = sys.executable if use_system_python else self.python_path(venv_root)
23742446
if not os.path.isfile(py_exe):
23752447
self._safe_log(
@@ -2417,7 +2489,10 @@ def _start_requirements_install(
24172489
self._req_use_system_python = bool(use_system_python)
24182490
self._pip_phase = "ensurepip"
24192491
self.progress_dialog = ProgressDialog(
2420-
"Installation des dépendances", self.parent
2492+
"Installation des dépendances", self.parent, cancelable=True
2493+
)
2494+
self._bind_cancel_for_dialog(
2495+
self.progress_dialog, "installation des dépendances"
24212496
)
24222497
self.progress_dialog.set_message("Activation de pip (ensurepip)...")
24232498
process = QProcess(self.parent)
@@ -2445,6 +2520,8 @@ def _start_requirements_install(
24452520
def _on_pip_output(self, process, error=False):
24462521
if getattr(self.parent, "_closing", False):
24472522
return
2523+
if self._is_cancel_requested():
2524+
return
24482525
data = (
24492526
process.readAllStandardError().data().decode()
24502527
if error
@@ -2466,6 +2543,9 @@ def _on_pip_output(self, process, error=False):
24662543
def _on_pip_finished(self, process, code, status):
24672544
if getattr(self.parent, "_closing", False):
24682545
return
2546+
if self._is_cancel_requested():
2547+
self._safe_log("ℹ️ Installation des dépendances annulée.")
2548+
return
24692549
phase = self._pip_phase
24702550
if phase == "ensurepip":
24712551
# Proceed to upgrade pip/setuptools/wheel regardless of ensurepip result
@@ -2611,6 +2691,9 @@ def has_active_tasks(self) -> bool:
26112691
return False
26122692

26132693
def terminate_tasks(self):
2694+
had_active = self.has_active_tasks()
2695+
if had_active:
2696+
self._cancel_requested = True
26142697
# Kill processes
26152698
for attr in [
26162699
"_venv_create_process",
@@ -2624,6 +2707,13 @@ def terminate_tasks(self):
26242707
proc.kill()
26252708
except Exception:
26262709
pass
2710+
2711+
if had_active:
2712+
self._safe_log(
2713+
"🛑 Opérations venv interrompues.",
2714+
"🛑 Venv operations interrupted.",
2715+
level="warning",
2716+
)
26272717
setattr(self, attr, None)
26282718
# Close dialogs
26292719
for dlg_attr in [
@@ -2977,6 +3067,7 @@ def create_venv_with_manager(
29773067
):
29783068
"""Create venv using the detected environment manager."""
29793069
try:
3070+
self._reset_cancel_state()
29803071
manager = self._detect_environment_manager(workspace_dir)
29813072

29823073
if not venv_path:
@@ -3019,7 +3110,12 @@ def create_venv_with_manager(
30193110

30203111
# Execute command
30213112
self.venv_progress_dialog = ProgressDialog(
3022-
f"Création du venv avec {manager}", self.parent
3113+
f"Création du venv avec {manager}",
3114+
self.parent,
3115+
cancelable=True,
3116+
)
3117+
self._bind_cancel_for_dialog(
3118+
self.venv_progress_dialog, f"création venv via {manager}"
30233119
)
30243120
self.venv_progress_dialog.set_message(f"Création du venv avec {manager}...")
30253121

@@ -3053,6 +3149,7 @@ def install_dependencies_with_manager(
30533149
):
30543150
"""Install dependencies using the detected environment manager."""
30553151
try:
3152+
self._reset_cancel_state()
30563153
manager = self._detect_environment_manager(workspace_dir)
30573154

30583155
if not venv_path:
@@ -3107,7 +3204,10 @@ def install_dependencies_with_manager(
31073204

31083205
# Execute command
31093206
self.progress_dialog = ProgressDialog(
3110-
f"Installation avec {manager}", self.parent
3207+
f"Installation avec {manager}", self.parent, cancelable=True
3208+
)
3209+
self._bind_cancel_for_dialog(
3210+
self.progress_dialog, f"installation via {manager}"
31113211
)
31123212
self.progress_dialog.set_message(
31133213
f"Installation des dépendances avec {manager}..."
@@ -3142,6 +3242,9 @@ def _on_manager_install_finished(self, process, code, status, manager):
31423242
"""Callback after manager-based installation."""
31433243
if getattr(self.parent, "_closing", False):
31443244
return
3245+
if self._is_cancel_requested():
3246+
self._safe_log(f"ℹ️ Installation via {manager} annulée.")
3247+
return
31453248

31463249
if code == 0:
31473250
self._safe_log(f"✅ Installation avec {manager} réussie.")

0 commit comments

Comments
 (0)