Skip to content

Commit 12a8299

Browse files
committed
Ajouter l'annulation des processus sys deps et leur arrêt propre à la fermeture
1 parent 4d914d2 commit 12a8299

2 files changed

Lines changed: 154 additions & 2 deletions

File tree

Core/Gui.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,14 @@ def _has_active_background_tasks(self) -> bool:
431431
return True
432432
except Exception:
433433
pass
434+
try:
435+
tasks = getattr(self, "_sysdep_tasks", None) or []
436+
for task in list(tasks):
437+
proc = task.get("process") if isinstance(task, dict) else None
438+
if proc is not None and proc.state() != proc.NotRunning:
439+
return True
440+
except Exception:
441+
pass
434442
return False
435443

436444
def _terminate_background_tasks(self):
@@ -440,6 +448,25 @@ def _terminate_background_tasks(self):
440448
self.venv_manager.terminate_tasks()
441449
except Exception:
442450
pass
451+
try:
452+
tasks = getattr(self, "_sysdep_tasks", None) or []
453+
for task in list(tasks):
454+
proc = task.get("process") if isinstance(task, dict) else None
455+
dlg = task.get("dialog") if isinstance(task, dict) else None
456+
try:
457+
if proc is not None and proc.state() != proc.NotRunning:
458+
proc.kill()
459+
except Exception:
460+
pass
461+
try:
462+
if dlg is not None:
463+
dlg.close()
464+
except Exception:
465+
pass
466+
if isinstance(tasks, list):
467+
tasks.clear()
468+
except Exception:
469+
pass
443470

444471
# =========================================================================
445472
# ÉVÉNEMENT DE FERMETURE
@@ -461,6 +488,20 @@ def closeEvent(self, event):
461488
)
462489
except Exception:
463490
pass
491+
try:
492+
tasks = getattr(self, "_sysdep_tasks", None) or []
493+
for task in list(tasks):
494+
if not isinstance(task, dict):
495+
continue
496+
proc = task.get("process")
497+
if proc is None or proc.state() == proc.NotRunning:
498+
continue
499+
if is_french:
500+
details.append(task.get("label_fr") or "dépendances système")
501+
else:
502+
details.append(task.get("label_en") or "system dependencies")
503+
except Exception:
504+
pass
464505

465506
if not is_french:
466507
mapping = {"compilation": "build"}

Core/sys_deps.py

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def _invoke_in_main_thread(fn, *args, **kwargs):
5353
class SysDependencyManager:
5454
def __init__(self, parent_widget=None):
5555
self.parent_widget = parent_widget
56+
self._cancelled_procs: set[int] = set()
5657
# Register list of system dependency tasks on the parent widget for global coordination
5758
try:
5859
if parent_widget is not None and not hasattr(
@@ -97,6 +98,81 @@ def _unregister_task(self, proc: QProcess) -> None:
9798
tasks.remove(t)
9899
except Exception:
99100
pass
101+
try:
102+
if proc is not None:
103+
self._cancelled_procs.discard(id(proc))
104+
except Exception:
105+
pass
106+
107+
def _mark_process_cancelled(self, proc: Optional[QProcess]) -> None:
108+
try:
109+
if proc is not None:
110+
self._cancelled_procs.add(id(proc))
111+
except Exception:
112+
pass
113+
114+
def _is_process_cancelled(self, proc: Optional[QProcess]) -> bool:
115+
try:
116+
return proc is not None and id(proc) in self._cancelled_procs
117+
except Exception:
118+
return False
119+
120+
def _log_cancel(self, fr: str, en: str) -> None:
121+
text = self.tr(fr, en)
122+
try:
123+
if (
124+
self.parent_widget is not None
125+
and hasattr(self.parent_widget, "_safe_log")
126+
and callable(self.parent_widget._safe_log)
127+
):
128+
self.parent_widget._safe_log(text)
129+
except Exception:
130+
pass
131+
try:
132+
self._dbg(text)
133+
except Exception:
134+
pass
135+
136+
def _cancel_task(
137+
self, proc: Optional[QProcess], dlg: Optional[ProgressDialog], label_fr: str, label_en: str
138+
) -> None:
139+
self._mark_process_cancelled(proc)
140+
self._log_cancel(
141+
f"🛑 Annulation demandée: {label_fr}.",
142+
f"🛑 Cancellation requested: {label_en}.",
143+
)
144+
try:
145+
if proc is not None and proc.state() != QProcess.NotRunning:
146+
proc.kill()
147+
except Exception:
148+
pass
149+
try:
150+
if dlg is not None:
151+
dlg.close()
152+
except Exception:
153+
pass
154+
try:
155+
if proc is not None:
156+
self._unregister_task(proc)
157+
except Exception:
158+
pass
159+
160+
def _bind_cancel_button(
161+
self,
162+
dlg: Optional[ProgressDialog],
163+
proc: Optional[QProcess],
164+
label_fr: str,
165+
label_en: str,
166+
) -> None:
167+
try:
168+
btn = getattr(dlg, "btn_cancel", None) if dlg is not None else None
169+
if btn is None:
170+
return
171+
btn.clicked.connect(
172+
lambda: self._cancel_task(proc, dlg, label_fr, label_en)
173+
)
174+
except Exception:
175+
pass
100176

101177
# ------------- Debug/telemetry helpers -------------
102178
def set_debug(self, enabled: bool = True) -> None:
@@ -532,14 +608,31 @@ def install_packages_windows(self, packages: list[dict]) -> Optional[QProcess]:
532608
"Installing Windows dependencies",
533609
),
534610
self.parent_widget,
611+
cancelable=True,
535612
)
536613
dlg.set_message(self.tr("Préparation…", "Preparing…"))
537614
dlg.progress.setRange(0, 0)
538615
dlg.show()
539616
queue = list(packages)
540617
proc = QProcess(self.parent_widget)
618+
state = {"cancelled": False}
619+
620+
def _cancel_winget():
621+
if state["cancelled"]:
622+
return
623+
state["cancelled"] = True
624+
self._cancel_task(proc, dlg, "installation winget", "winget installation")
625+
626+
try:
627+
btn = getattr(dlg, "btn_cancel", None)
628+
if btn is not None:
629+
btn.clicked.connect(_cancel_winget)
630+
except Exception:
631+
pass
541632

542633
def _start_next():
634+
if state["cancelled"] or self._is_process_cancelled(proc):
635+
return
543636
if not queue:
544637
try:
545638
dlg.close()
@@ -581,6 +674,8 @@ def _start_next():
581674
proc.start()
582675

583676
def _on_output(p: QProcess, error: bool = False):
677+
if state["cancelled"] or self._is_process_cancelled(proc):
678+
return
584679
try:
585680
data = (
586681
p.readAllStandardError().data().decode()
@@ -601,6 +696,8 @@ def _on_output(p: QProcess, error: bool = False):
601696
pass
602697

603698
def _on_finished(_ec, _es):
699+
if state["cancelled"] or self._is_process_cancelled(proc):
700+
return
604701
_start_next()
605702

606703
proc.readyReadStandardOutput.connect(lambda p=proc: _on_output(p, False))
@@ -632,18 +729,25 @@ def start_process_with_progress(
632729
Le dialogue se ferme automatiquement à la fin du processus.
633730
"""
634731
try:
635-
dlg = ProgressDialog(self.tr(title_fr, title_en), self.parent_widget)
732+
dlg = ProgressDialog(
733+
self.tr(title_fr, title_en), self.parent_widget, cancelable=True
734+
)
636735
dlg.set_message(self.tr(start_msg_fr, start_msg_en))
637736
dlg.progress.setRange(0, 0) # indéterminé
638737
dlg.show()
639738
proc = QProcess(self.parent_widget)
739+
self._bind_cancel_button(
740+
dlg, proc, "installation des dépendances", "dependencies installation"
741+
)
640742
if cwd:
641743
proc.setWorkingDirectory(cwd)
642744
proc.setProgram(program)
643745
proc.setArguments(list(args or []))
644746

645747
# Mise à jour du message avec la dernière ligne reçue
646748
def _on_output(p: QProcess, error: bool = False):
749+
if self._is_process_cancelled(proc):
750+
return
647751
try:
648752
data = (
649753
p.readAllStandardError().data().decode()
@@ -710,11 +814,16 @@ def run_sudo_shell_with_progress(
710814
"This sudo operation is supported on Linux only.",
711815
)
712816
return None
713-
dlg = ProgressDialog(self.tr(title_fr, title_en), self.parent_widget)
817+
dlg = ProgressDialog(
818+
self.tr(title_fr, title_en), self.parent_widget, cancelable=True
819+
)
714820
dlg.set_message(self.tr(start_msg_fr, start_msg_en))
715821
dlg.progress.setRange(0, 0)
716822
dlg.show()
717823
proc = QProcess(self.parent_widget)
824+
self._bind_cancel_button(
825+
dlg, proc, "installation des dépendances", "dependencies installation"
826+
)
718827
if cwd:
719828
proc.setWorkingDirectory(cwd)
720829
# Utiliser bash -lc pour exécuter la chaîne
@@ -723,6 +832,8 @@ def run_sudo_shell_with_progress(
723832

724833
# maj message sur sortie
725834
def _on_output(p: QProcess, error: bool = False):
835+
if self._is_process_cancelled(proc):
836+
return
726837
try:
727838
data = (
728839
p.readAllStandardError().data().decode()

0 commit comments

Comments
 (0)