Extrait les appels macOS derriere le contrat Systeme

- systeme/macos.py : SystemeMacOS (sons afplay, modificateurs Quartz, chemins,
  verrou d'instance fcntl) implementant le contrat Systeme.
- systeme/__init__.py : choisir_systeme() fabrique selon sys.platform,
  refuse clairement une plateforme inconnue.
- core.py : verrou d'instance (fcntl, LOCK_PATH, acquire_single_instance_lock)
  retire ; import os retire aussi, verifie inutilise ailleurs dans le fichier.
- app.py : passe par choisir_systeme().acquerir_verrou_instance(dossier_donnees()
  + instance.lock) au lieu de core.acquire_single_instance_lock. Seul changement
  dans ce fichier, nom de fichier inchange.
- tests/test_single_instance.py : reoriente vers choisir_systeme(), memes
  assertions et meme nombre de tests.
- tests/test_systeme.py (nouveau) : verrou du test_le_verrou_refuse_une_seconde_prise
  garde desormais la reference au premier verrou — sans variable, CPython le
  garbage-collectait entre les deux assert (refcount 0 -> fermeture -> verrou
  relache), le test passait pour la mauvaise raison.

89 tests verts (84 + 5).
This commit is contained in:
2026-08-23 17:57:52 +02:00
parent 434944def6
commit 5c4477fe1d
6 changed files with 125 additions and 43 deletions
-31
View File
@@ -3,8 +3,6 @@
Tout ce qui est ici est testable sans micro, sans clavier, sans modèle.
"""
import fcntl
import os
import re
CONFIG = {
@@ -196,33 +194,4 @@ def clean_transcript(text):
return re.sub(r"\s+", " ", text).strip()
LOCK_PATH = os.path.expanduser("~/Library/Application Support/Zonza/instance.lock")
def acquire_single_instance_lock(path=LOCK_PATH):
"""Prend un verrou exclusif non bloquant ; renvoie le fichier, ou None si une
autre instance le detient deja.
Pourquoi : deux bundles Zonza de meme identifiant (dont un fantome reste dans
la Corbeille mais toujours enregistre aupres de LaunchServices) pouvaient
tourner en meme temps. Chacun chargeait son propre modele MLX-Whisper et
captait la MEME hotkey globale : un appui, deux transcriptions, machine
saturee. Purger le fantome corrige l'incident ; ce verrou corrige la classe.
Le handle doit rester vivant tant que l'app tourne : le verrou tombe a la
fermeture du fichier, et le noyau le libere si le processus meurt brutalement
(donc pas de verrou orphelin apres un plantage).
"""
parent = os.path.dirname(path)
if parent:
os.makedirs(parent, exist_ok=True)
handle = open(path, "a")
try:
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
handle.close()
return None
return handle
CONFIG["initial_prompt"] = build_initial_prompt()