fletchtime 0.2.3__tar.gz → 0.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fletchtime-0.2.3/src/fletchtime.egg-info → fletchtime-0.2.5}/PKG-INFO +16 -7
- {fletchtime-0.2.3 → fletchtime-0.2.5}/README.md +15 -6
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/architecture.md +128 -1
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/dev-guide/index.md +34 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/index.md +1 -0
- fletchtime-0.2.5/docs/premier-club.md +113 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/roadmap.md +53 -7
- {fletchtime-0.2.3 → fletchtime-0.2.5}/fletchtime.spec +7 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/__main__.py +97 -14
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/_version.py +3 -3
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/engine/models.py +9 -9
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/engine/sequence.py +3 -2
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/gui.py +110 -4
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/logging_setup.py +28 -3
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/runtime.py +14 -2
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/server/config_store.py +41 -4
- fletchtime-0.2.5/src/fletchtime/server/http_static.py +159 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/server/match_server.py +67 -44
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/control.html +19 -2
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/display.html +48 -11
- fletchtime-0.2.5/src/fletchtime/web/logo.ico +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5/src/fletchtime.egg-info}/PKG-INFO +16 -7
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime.egg-info/SOURCES.txt +3 -1
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime.egg-info/scm_file_list.json +3 -1
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime.egg-info/scm_version.json +2 -2
- {fletchtime-0.2.3 → fletchtime-0.2.5}/tests/test_config_store.py +42 -0
- fletchtime-0.2.5/tests/test_main_cli.py +152 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/tests/test_match_server.py +52 -1
- {fletchtime-0.2.3 → fletchtime-0.2.5}/tests/test_runtime.py +13 -0
- fletchtime-0.2.3/src/fletchtime/server/http_static.py +0 -83
- fletchtime-0.2.3/src/fletchtime/web/_defaults/gui/theme.json +0 -64
- {fletchtime-0.2.3 → fletchtime-0.2.5}/.github/workflows/build.yml +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/.github/workflows/docs.yml +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/.github/workflows/test.yml +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/.gitignore +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/CONTRIBUTING.md +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/LICENSE +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/REMERCIEMENTS.md +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/config/app.toml +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/config/flint.toml +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/config/indoor.toml +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/demo.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/_static/logo.svg +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/api-reference.md +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/conf.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/remerciements.md +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/requirements.txt +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/specifications.md +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/docs/user-guide/index.md +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/pyproject.toml +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/run_server.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/run_tests.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/scripts/generate_classic_sounds.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/setup.cfg +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/__init__.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/engine/__init__.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/engine/engine.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/engine/modes/__init__.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/engine/modes/base.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/engine/modes/flint.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/engine/modes/indoor.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/engine/turn_modes.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/server/__init__.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/server/ws_server.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/__init__.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/banners/README.md +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/club/README.md +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/README.md +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/classic/countdown_tick.wav +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/classic/emergency_end.wav +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/classic/emergency_start.wav +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/classic/end_of_match.wav +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/classic/end_of_volee.wav +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/classic/pause_end.wav +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/classic/pause_start.wav +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/classic/prep_start.wav +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/classic/shoot_start.wav +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/sounds/packs/classic/warning_orange.wav +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/targets/flint_20cm_4spot.jpg +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/targets/flint_35cm_1spot.jpg +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/targets/indoor_compound.jpg +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/_defaults/targets/indoor_recurve.jpg +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/config.html +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/i18n.js +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/index.html +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/logo.svg +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/manual.html +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/screenshots/config.png +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/screenshots/control.png +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/screenshots/display.png +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/screenshots/index.png +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime/web/theme.js +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime.egg-info/dependency_links.txt +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime.egg-info/entry_points.txt +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime.egg-info/requires.txt +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/src/fletchtime.egg-info/top_level.txt +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/tests/test_engine.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/tests/test_flint_mode.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/tests/test_indoor_mode.py +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/web/assets/banners/README.md +0 -0
- {fletchtime-0.2.3 → fletchtime-0.2.5}/web/assets/sounds/packs/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fletchtime
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Chronométrage de compétitions d'archerie FFTL (Indoor, Flint) -- serveur + interfaces web incluses
|
|
5
5
|
License: GPL-3.0-or-later
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -38,6 +38,13 @@ compris Pydroid 3. Ajoute `--headless` pour retrouver l'ancien mode
|
|
|
38
38
|
terminal (aussi utilisé automatiquement si la fenêtre ne peut pas se
|
|
39
39
|
charger, ex. `customtkinter` absent).
|
|
40
40
|
|
|
41
|
+
En mode terminal, `fletchtime --headless --help` liste toutes les
|
|
42
|
+
options -- notamment `-v`/`--verbose` et `-d`/`--debug` (plus de détail
|
|
43
|
+
dans le journal), et `--http-port`/`--ws-port` pour remplacer les ports
|
|
44
|
+
configurés le temps d'un seul lancement (ex. plusieurs salles de
|
|
45
|
+
compétition sur un même PC via un script, sans dossier séparé par salle
|
|
46
|
+
-- voir plus bas pour l'approche par copie de dossier).
|
|
47
|
+
|
|
41
48
|
## Installation
|
|
42
49
|
|
|
43
50
|
Trois façons d'obtenir et faire tourner FletchTime, selon ton matériel --
|
|
@@ -105,12 +112,14 @@ Autoriser une application via le pare-feu* -- coche `FletchTime.exe` pour
|
|
|
105
112
|
les réseaux privés.
|
|
106
113
|
|
|
107
114
|
**Ports réseau utilisés** : **8000** en HTTP (pages web) et **8765** en
|
|
108
|
-
WebSocket (synchronisation temps réel) -- deux ports séparés,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
WebSocket (synchronisation temps réel) par défaut -- deux ports séparés,
|
|
116
|
+
tous deux nécessaires, modifiables (fenêtre graphique, `config/gui.toml`,
|
|
117
|
+
ou `--http-port`/`--ws-port` en ligne de commande). Si le serveur tourne
|
|
118
|
+
dans un conteneur/VM (Docker, WSL2...), les deux doivent être redirigés
|
|
119
|
+
vers l'hôte, pas seulement le port HTTP : sans le port WebSocket, les
|
|
120
|
+
pages se chargent normalement mais restent bloquées sur "en attente de
|
|
121
|
+
connexion" indéfiniment (la synchronisation temps réel ne peut jamais
|
|
122
|
+
s'établir).
|
|
114
123
|
|
|
115
124
|
Pour construire ces exécutables toi-même :
|
|
116
125
|
voir `.github/workflows/build.yml` et `fletchtime.spec` (PyInstaller). Un
|
|
@@ -17,6 +17,13 @@ compris Pydroid 3. Ajoute `--headless` pour retrouver l'ancien mode
|
|
|
17
17
|
terminal (aussi utilisé automatiquement si la fenêtre ne peut pas se
|
|
18
18
|
charger, ex. `customtkinter` absent).
|
|
19
19
|
|
|
20
|
+
En mode terminal, `fletchtime --headless --help` liste toutes les
|
|
21
|
+
options -- notamment `-v`/`--verbose` et `-d`/`--debug` (plus de détail
|
|
22
|
+
dans le journal), et `--http-port`/`--ws-port` pour remplacer les ports
|
|
23
|
+
configurés le temps d'un seul lancement (ex. plusieurs salles de
|
|
24
|
+
compétition sur un même PC via un script, sans dossier séparé par salle
|
|
25
|
+
-- voir plus bas pour l'approche par copie de dossier).
|
|
26
|
+
|
|
20
27
|
## Installation
|
|
21
28
|
|
|
22
29
|
Trois façons d'obtenir et faire tourner FletchTime, selon ton matériel --
|
|
@@ -84,12 +91,14 @@ Autoriser une application via le pare-feu* -- coche `FletchTime.exe` pour
|
|
|
84
91
|
les réseaux privés.
|
|
85
92
|
|
|
86
93
|
**Ports réseau utilisés** : **8000** en HTTP (pages web) et **8765** en
|
|
87
|
-
WebSocket (synchronisation temps réel) -- deux ports séparés,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
WebSocket (synchronisation temps réel) par défaut -- deux ports séparés,
|
|
95
|
+
tous deux nécessaires, modifiables (fenêtre graphique, `config/gui.toml`,
|
|
96
|
+
ou `--http-port`/`--ws-port` en ligne de commande). Si le serveur tourne
|
|
97
|
+
dans un conteneur/VM (Docker, WSL2...), les deux doivent être redirigés
|
|
98
|
+
vers l'hôte, pas seulement le port HTTP : sans le port WebSocket, les
|
|
99
|
+
pages se chargent normalement mais restent bloquées sur "en attente de
|
|
100
|
+
connexion" indéfiniment (la synchronisation temps réel ne peut jamais
|
|
101
|
+
s'établir).
|
|
93
102
|
|
|
94
103
|
Pour construire ces exécutables toi-même :
|
|
95
104
|
voir `.github/workflows/build.yml` et `fletchtime.spec` (PyInstaller). Un
|
|
@@ -28,7 +28,7 @@ installation logicielle sur les tablettes d'affichage.
|
|
|
28
28
|
| Composant | Choix | Justification |
|
|
29
29
|
|---|---|---|
|
|
30
30
|
| Backend WebSocket | Paquet `websockets` (asyncio) | Pas de FastAPI/Pydantic (dépendance Rust `pydantic-core` à risque sur Android/Pydroid) ni d'uvicorn `[standard]` (extensions C `uvloop`/`httptools`) ; `websockets` a un fallback pur Python si son extension C optionnelle ne compile pas — installable de façon fiable sur Pydroid 3. |
|
|
31
|
-
| Serveur HTTP statique | `http.server` (stdlib), port **8000** | Sert les pages et assets sur un port séparé du WebSocket (port **8765**) -- évite l'API instable de combinaison HTTP+WS selon les versions de `websockets` ; utilisé aussi pour la découverte de fichiers (bannières, packs de sons) via le listing de répertoire natif. **Les deux ports doivent être accessibles** depuis les écrans/postes clients (pare-feu, redirection de port si le serveur tourne dans un conteneur/VM -- ex. Docker, WSL2) : rediriger seulement le
|
|
31
|
+
| Serveur HTTP statique | `http.server` (stdlib), port **8000** par défaut | Sert les pages et assets sur un port séparé du WebSocket (port **8765** par défaut) -- évite l'API instable de combinaison HTTP+WS selon les versions de `websockets` ; utilisé aussi pour la découverte de fichiers (bannières, packs de sons) via le listing de répertoire natif. Les deux ports sont modifiables (fenêtre graphique, `config/gui.toml`, ou `--http-port`/`--ws-port` en ligne de commande -- voir {doc}`dev-guide/index`), notamment pour plusieurs salles de compétition sur un même PC. **Les deux ports doivent être accessibles** depuis les écrans/postes clients (pare-feu, redirection de port si le serveur tourne dans un conteneur/VM -- ex. Docker, WSL2) : rediriger seulement le port HTTP charge les pages mais laisse la synchronisation temps réel bloquée ("en attente de connexion" indéfiniment). |
|
|
32
32
|
| Communication temps réel | WebSocket | Évite la dérive du polling, tous les écrans restent synchronisés à la seconde près. |
|
|
33
33
|
| Frontend | HTML/CSS/JS vanilla | Pas de build, une tablette ouvre juste une URL. |
|
|
34
34
|
| Config des modes (Indoor/Flint) | Fichiers **TOML** (`config/*.toml`) | Lu via `tomllib`, stdlib depuis Python 3.11 (donc Pydroid) -- zéro dépendance. Écriture via un petit sérialiseur maison (pas de support d'écriture en stdlib). |
|
|
@@ -343,6 +343,60 @@ confirmer le rendu et l'ergonomie tactile -- voir aussi le piège
|
|
|
343
343
|
PyInstaller/`customtkinter` documenté dans {doc}`dev-guide/index`.
|
|
344
344
|
```
|
|
345
345
|
|
|
346
|
+
## Résilience de la boucle de décompte
|
|
347
|
+
|
|
348
|
+
`MatchServer.tick_loop()` capture désormais toute exception imprévue en
|
|
349
|
+
son sein (journalisée avec la trace complète) plutôt que de laisser
|
|
350
|
+
mourir la boucle silencieusement -- filet de sécurité ajouté suite à un
|
|
351
|
+
symptôme signalé en pratique : le chrono se figeait indéfiniment, sans
|
|
352
|
+
aucune erreur visible, le reste du serveur (connexions, réponse aux
|
|
353
|
+
commandes) continuant de fonctionner normalement à côté.
|
|
354
|
+
|
|
355
|
+
```{important}
|
|
356
|
+
**Cause la plus probable identifiée** : sous Windows, remplacer un
|
|
357
|
+
fichier (`Path.replace`) peut échouer avec une "violation de partage"
|
|
358
|
+
si un autre processus a le fichier cible ouvert au même instant
|
|
359
|
+
(antivirus, surveillance de fichiers d'un IDE, Git Bash...) -- une
|
|
360
|
+
différence fondamentale avec la sémantique POSIX (Linux/macOS), où ceci
|
|
361
|
+
n'arrive jamais. Observé une fois en pratique, précisément sur
|
|
362
|
+
`config/match_state.json` -- écrit à chaque tick depuis la persistance
|
|
363
|
+
après plantage (voir plus bas). Sans gestion d'erreur, cette exception
|
|
364
|
+
tuait silencieusement `tick_loop` pour de bon : exactement le symptôme
|
|
365
|
+
rapporté (gel permanent, aucune déconnexion, se produisant aussi bien
|
|
366
|
+
en fenêtre graphique qu'en mode terminal -- sans lien réel avec le
|
|
367
|
+
focus d'une fenêtre, malgré la corrélation observée au départ).
|
|
368
|
+
|
|
369
|
+
Deux correctifs complémentaires : ce filet dans `tick_loop` (n'importe
|
|
370
|
+
quelle exception, pas seulement celle-ci), et
|
|
371
|
+
`config_store.save_match_snapshot` qui retente quelques fois avant
|
|
372
|
+
d'abandonner proprement (jamais d'exception qui remonterait perturber
|
|
373
|
+
la diffusion de l'état aux écrans pour ce tick). Testé concrètement :
|
|
374
|
+
un échec transitoire (une fois puis réussite) est absorbé et le
|
|
375
|
+
contenu final reste correct ; un échec permanent (toutes les tentatives
|
|
376
|
+
échouent) est abandonné proprement, sans jamais lever d'exception.
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Statut technique exposé via HTTP (`/api/status`)
|
|
380
|
+
|
|
381
|
+
Les mêmes données déjà affichées dans `control.html` (écrans connectés,
|
|
382
|
+
mode actif, phase en cours, pack de sons, mot de passe configuré ou non)
|
|
383
|
+
sont aussi exposées via un simple GET HTTP, lues directement depuis
|
|
384
|
+
l'instance `MatchServer` partagée avec le serveur WebSocket (voir
|
|
385
|
+
`ServerRuntime`, qui construit maintenant ce `MatchServer` une seule fois
|
|
386
|
+
et le fait circuler vers les deux serveurs plutôt que de le laisser
|
|
387
|
+
`run_ws_server` en créer un nouveau à chaque démarrage).
|
|
388
|
+
|
|
389
|
+
Utilisé par la fenêtre graphique pour afficher ce même statut sans
|
|
390
|
+
dupliquer la logique de rendu HTML -- interrogé par sondage périodique
|
|
391
|
+
(toutes les 2s) depuis un thread séparé plutôt qu'en temps réel via une
|
|
392
|
+
vraie connexion WebSocket, volontairement : pas besoin de la précision
|
|
393
|
+
temps réel d'une vraie connexion juste pour un affichage de statut, et
|
|
394
|
+
ça évite de dupliquer toute la logique de reconnexion/état déjà présente
|
|
395
|
+
côté web. La requête HTTP elle-même tourne toujours dans un thread à
|
|
396
|
+
part, jamais directement depuis le thread principal de la fenêtre (une
|
|
397
|
+
requête bloquante, même locale, gèlerait sinon l'interface le temps de
|
|
398
|
+
sa réponse).
|
|
399
|
+
|
|
346
400
|
## Journal applicatif persistant
|
|
347
401
|
|
|
348
402
|
En plus du journal affiché dans la fenêtre graphique (en mémoire,
|
|
@@ -370,6 +424,21 @@ volatile de la fenêtre.
|
|
|
370
424
|
dernières secondes de chaque volée -- noierait le journal sans valeur
|
|
371
425
|
diagnostique ajoutée).
|
|
372
426
|
|
|
427
|
+
```{important}
|
|
428
|
+
`configure_logging` prend un `console_level` séparé du niveau du fichier
|
|
429
|
+
(toujours INFO par défaut, lui) -- la fenêtre graphique doit l'appeler
|
|
430
|
+
avec `console_level=logging.INFO` explicitement, **pas** le défaut
|
|
431
|
+
(`WARNING`, pensé pour un terminal silencieux par défaut, voir
|
|
432
|
+
`fletchtime.__main__`, `-v`/`--verbose`). Un oubli de ce paramètre a
|
|
433
|
+
laissé le widget de journal de la fenêtre silencieux en usage normal
|
|
434
|
+
pendant un temps -- tous les journaux applicatifs ci-dessus sont à
|
|
435
|
+
INFO, donc filtrés par le `WARNING` par défaut, exactement l'inverse de
|
|
436
|
+
ce que ce widget est censé montrer. Confirmé par comparaison directe
|
|
437
|
+
avant/après correctif : file d'attente vide avant, message présent
|
|
438
|
+
après, avec un appel par ailleurs identique.
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
|
|
373
442
|
```{important}
|
|
374
443
|
Le mot de passe (action `authenticate`) n'est **jamais** journalisé -- le
|
|
375
444
|
code ne journalise jamais `data` tel quel, seulement le nom de l'action
|
|
@@ -502,6 +571,52 @@ Le poste de contrôle, à l'inverse, affiche une bannière large et alarmante
|
|
|
502
571
|
(pas discrète) en cas de coupure -- c'est le responsable du chronométrage
|
|
503
572
|
qui doit être alerté clairement, pas les archers.
|
|
504
573
|
|
|
574
|
+
## Synchronisation du diaporama de l'écran neutre
|
|
575
|
+
|
|
576
|
+
L'écran neutre (hors concours, ou après sa fin) alterne logo/horloge et
|
|
577
|
+
bannières sponsors -- voir `display.html`, `showSlideshowStep`. Aucune
|
|
578
|
+
coordination serveur pour ça : chaque écran calcule sa slide actuelle en
|
|
579
|
+
divisant l'horloge murale (`Date.now()`) par la durée d'une slide, plutôt
|
|
580
|
+
que d'incrémenter un compteur local à partir de 0 à son propre démarrage.
|
|
581
|
+
|
|
582
|
+
```{important}
|
|
583
|
+
Une première version utilisait un compteur local (`slideshowStep`,
|
|
584
|
+
incrémenté par `setInterval`) -- deux écrans qui chargeaient ou se
|
|
585
|
+
reconnectaient à des instants différents affichaient alors des slides
|
|
586
|
+
différentes au même moment, chacun étant reparti de 0 à son propre
|
|
587
|
+
démarrage. Corrigé en dérivant la slide actuelle de l'horloge murale
|
|
588
|
+
(`Math.floor(Date.now() / SLIDE_DURATION_MS) % totalSlides`) : deux
|
|
589
|
+
écrans avec des horloges système raisonnablement synchronisées (le cas
|
|
590
|
+
normal sur un même réseau local) calculent alors la même slide,
|
|
591
|
+
indépendamment de quand chacun a démarré. Vérifié avec un vrai navigateur
|
|
592
|
+
(Chromium via Playwright) : deux pages chargées à 3 secondes d'écart
|
|
593
|
+
affichent bien la même slide.
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## Découverte du port WebSocket côté client
|
|
597
|
+
|
|
598
|
+
`display.html` et `control.html` ne connaissent pas à l'avance le port
|
|
599
|
+
WebSocket à utiliser : depuis que les ports sont devenus modifiables
|
|
600
|
+
(voir la fenêtre graphique et `config/gui.toml`, pensé pour plusieurs
|
|
601
|
+
salles de compétition sur un même PC), le coder en dur côté client
|
|
602
|
+
casserait silencieusement toute page si le port avait été changé.
|
|
603
|
+
|
|
604
|
+
Chaque page interroge `/api/version` (servi par le même serveur HTTP qui
|
|
605
|
+
vient de la servir, donc forcément sur le bon port) avant d'ouvrir sa
|
|
606
|
+
connexion WebSocket -- la réponse inclut `ws_port`, le port réellement
|
|
607
|
+
configuré (`ServerRuntime.ws_port`, plombé jusqu'à
|
|
608
|
+
`http_static.start_http_server`). Un échec de cette requête (réseau,
|
|
609
|
+
serveur non démarré) se rabat silencieusement sur `8765` -- l'ancien
|
|
610
|
+
port fixe, qui reste une valeur par défaut raisonnable, jamais une
|
|
611
|
+
erreur bloquante pour l'utilisateur.
|
|
612
|
+
|
|
613
|
+
```{note}
|
|
614
|
+
Vérifié avec un vrai navigateur (Chromium via Playwright), pas seulement
|
|
615
|
+
en théorie : les deux pages utilisent bien le port récupéré
|
|
616
|
+
dynamiquement, et se rabattent proprement sur 8765 sans planter quand
|
|
617
|
+
`/api/version` échoue.
|
|
618
|
+
```
|
|
619
|
+
|
|
505
620
|
## Multi-écrans et ciblage
|
|
506
621
|
|
|
507
622
|
Chaque écran se connecte au WebSocket et s'enregistre avec son numéro de lane
|
|
@@ -519,3 +634,15 @@ La miniature d'aperçu de `control.html` est un vrai `display.html` chargé
|
|
|
519
634
|
dans une `<iframe>` (mise à l'échelle en CSS) plutôt qu'une logique de rendu
|
|
520
635
|
dupliquée -- elle s'enregistre avec la lane spéciale `"apercu"`, exclue du
|
|
521
636
|
comptage des écrans connectés.
|
|
637
|
+
|
|
638
|
+
```{note}
|
|
639
|
+
**Cette même lane `"apercu"` ne joue jamais de son** (voir `display.html`,
|
|
640
|
+
`soundEnabled`) : sans ça, ouvrir la page de contrôle et un vrai écran
|
|
641
|
+
d'affichage sur le même PC faisait entendre chaque son deux fois --
|
|
642
|
+
l'aperçu est une simple vue visuelle pour le responsable du
|
|
643
|
+
chronométrage, pas un écran destiné aux archers. Un paramètre d'URL
|
|
644
|
+
`?mute=1` permet en plus de couper le son sur n'importe quel autre onglet
|
|
645
|
+
ouvert en trop sur la même machine (ex. pour surveiller une autre lane
|
|
646
|
+
sans dupliquer le son de l'écran qui joue réellement pour les archers).
|
|
647
|
+
```
|
|
648
|
+
|
|
@@ -133,6 +133,40 @@ dernier commit de `main`, seulement la dernière version taguée -- voir
|
|
|
133
133
|
la note correspondante sur {doc}`../index`.
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
## Options de la ligne de commande (mode terminal)
|
|
137
|
+
|
|
138
|
+
`fletchtime --headless --help` (ou `python -m fletchtime --headless
|
|
139
|
+
--help`) affiche la liste complète, mais résumé ici pour référence
|
|
140
|
+
rapide -- voir `fletchtime/__main__.py`, `_build_arg_parser`.
|
|
141
|
+
|
|
142
|
+
| Option | Effet |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `-h`, `--help` | Affiche l'aide et quitte. |
|
|
145
|
+
| `-V`, `--version` | Affiche la version et quitte. |
|
|
146
|
+
| `--headless`, `--no-gui` | Mode terminal, sans fenêtre graphique. |
|
|
147
|
+
| `-v`, `--verbose` | Affiche les journaux applicatifs (commandes reçues, (dé)connexions...) dans le terminal, pas seulement dans le fichier. |
|
|
148
|
+
| `-d`, `--debug` | Journalisation la plus détaillée possible, fichier compris -- implique `--verbose`. |
|
|
149
|
+
| `--http-port PORT` | Remplace le port HTTP configuré, pour cette exécution seulement. |
|
|
150
|
+
| `--ws-port PORT` | Remplace le port WebSocket configuré, pour cette exécution seulement. |
|
|
151
|
+
|
|
152
|
+
```{note}
|
|
153
|
+
`--http-port`/`--ws-port` **ne modifient jamais** `config/gui.toml` --
|
|
154
|
+
un remplacement ponctuel (utile pour un lancement scripté/CI, ou
|
|
155
|
+
plusieurs salles de compétition sur un même PC sans dossier séparé par
|
|
156
|
+
salle), pas un changement persistant. Pour un réglage durable, voir la
|
|
157
|
+
fenêtre graphique (section Ports) ou éditer `config/gui.toml`
|
|
158
|
+
directement.
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Niveaux de journalisation, fichier et terminal indépendants l'un de
|
|
162
|
+
l'autre** (voir `fletchtime.logging_setup.configure_logging`) : le
|
|
163
|
+
fichier de journal (`logs/fletchtime.log`) reste toujours à INFO par
|
|
164
|
+
défaut, quelle que soit la commande utilisée pour lancer FletchTime --
|
|
165
|
+
le diagnostic après-coup d'un concours ne doit pas dépendre de si
|
|
166
|
+
quelqu'un a pensé à ajouter `-v`. Seul le terminal respecte
|
|
167
|
+
`-v`/`--verbose` (silencieux par défaut, WARNING). `--debug` élève les
|
|
168
|
+
deux au niveau DEBUG.
|
|
169
|
+
|
|
136
170
|
## Architecture générale
|
|
137
171
|
|
|
138
172
|
Voir {doc}`../architecture` pour le détail complet. En résumé :
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Découvrir FletchTime pour son club
|
|
2
|
+
|
|
3
|
+
Cette page s'adresse à quelqu'un qui découvre FletchTime pour la première
|
|
4
|
+
fois -- un⋅e archer⋅ère, un⋅e responsable de club. Pas besoin de
|
|
5
|
+
connaître quoi que ce soit du projet au préalable.
|
|
6
|
+
|
|
7
|
+
## C'est quoi
|
|
8
|
+
|
|
9
|
+
FletchTime est un logiciel de chronométrage pour les compétitions
|
|
10
|
+
d'archerie FFTL (Indoor et Flint) : il gère le décompte du temps de tir,
|
|
11
|
+
les phases de préparation et de repos, les sons qui rythment le
|
|
12
|
+
concours, sur autant d'écrans que nécessaire. Gratuit, open source, né
|
|
13
|
+
de l'usage réel d'un club.
|
|
14
|
+
|
|
15
|
+
## Pourquoi s'en servir
|
|
16
|
+
|
|
17
|
+
::::{grid} 2
|
|
18
|
+
:gutter: 3
|
|
19
|
+
|
|
20
|
+
:::{grid-item-card} 📺 Un écran par pas de tir
|
|
21
|
+
Pas juste un chrono près du starter -- chaque archer voit le temps
|
|
22
|
+
restant sans avoir à se retourner.
|
|
23
|
+
:::
|
|
24
|
+
|
|
25
|
+
:::{grid-item-card} 🔊 Les sons rythment le concours tout seuls
|
|
26
|
+
Début de préparation, passage à l'orange, fin de volée... plus besoin
|
|
27
|
+
qu'une personne déclenche chaque signal à la main.
|
|
28
|
+
:::
|
|
29
|
+
|
|
30
|
+
:::{grid-item-card} 💻 Du matériel qu'un club a probablement déjà
|
|
31
|
+
Un vieux PC, des tablettes ou téléphones pour les écrans -- pas de
|
|
32
|
+
matériel spécialisé à acheter.
|
|
33
|
+
:::
|
|
34
|
+
|
|
35
|
+
:::{grid-item-card} 🔌 Résiste aux coupures et aux plantages
|
|
36
|
+
Le chrono ne perd pas le fil, la reprise se fait sans intervention
|
|
37
|
+
manuelle -- voir {doc}`architecture` pour le détail technique.
|
|
38
|
+
:::
|
|
39
|
+
|
|
40
|
+
::::
|
|
41
|
+
|
|
42
|
+
## Ce qu'il faut avant de commencer
|
|
43
|
+
|
|
44
|
+
- Un appareil pour héberger le serveur (PC ou téléphone selon la méthode
|
|
45
|
+
d'installation choisie) -- pas besoin d'être puissant.
|
|
46
|
+
- Un réseau WiFi local reliant cet appareil et les écrans.
|
|
47
|
+
- Des écrans pour l'affichage : tablettes, téléphones, ou même un vieux
|
|
48
|
+
moniteur relié à un PC secondaire.
|
|
49
|
+
- Aucune compétence technique nécessaire pour l'usage courant -- voir
|
|
50
|
+
plus bas si l'installation elle-même te semble intimidante.
|
|
51
|
+
|
|
52
|
+
## Premiers pas
|
|
53
|
+
|
|
54
|
+
1. **Installer** : plusieurs façons possibles selon le matériel -- voir le
|
|
55
|
+
[README](https://github.com/MrFanghoDev/fletchtime#installation)
|
|
56
|
+
pour le détail de chacune.
|
|
57
|
+
2. **Premier lancement** : une fenêtre s'ouvre avec les adresses à
|
|
58
|
+
utiliser depuis les autres appareils du réseau.
|
|
59
|
+
3. **Ouvrir la page de contrôle** depuis l'appareil hôte ou n'importe
|
|
60
|
+
quel appareil du réseau, choisir le mode (Indoor ou Flint), ajuster
|
|
61
|
+
les réglages si besoin (temps de tir, nombre de volées...).
|
|
62
|
+
4. **Ouvrir la page d'affichage** sur chaque écran destiné aux archers.
|
|
63
|
+
5. **Faire un match d'essai** avant le premier vrai concours -- le
|
|
64
|
+
temps de se familiariser avec les boutons (démarrer, pause, urgence)
|
|
65
|
+
sans pression.
|
|
66
|
+
|
|
67
|
+
Une fois ces cinq étapes passées une fois, le pilotage d'un vrai
|
|
68
|
+
concours se résume à quelques clics : voir le manuel utilisateur
|
|
69
|
+
intégré à l'application (accessible depuis sa page d'accueil) pour le
|
|
70
|
+
détail de chaque réglage et bouton.
|
|
71
|
+
|
|
72
|
+
## Peut-on lui faire confiance
|
|
73
|
+
|
|
74
|
+
Question légitime avant de l'utiliser en compétition officielle :
|
|
75
|
+
|
|
76
|
+
- **Open source** : le code est public, inspectable par qui veut --
|
|
77
|
+
rien de caché.
|
|
78
|
+
- **Testé en conditions réelles de concours**, pas seulement "ça
|
|
79
|
+
compile" -- voir {doc}`roadmap` pour l'historique des versions et ce
|
|
80
|
+
qui a été vérifié en pratique.
|
|
81
|
+
- **Récupère après un plantage ou un redémarrage du serveur** sans
|
|
82
|
+
perdre la progression du match en cours.
|
|
83
|
+
- Reste un projet de club, sans obligation de résultat ni support
|
|
84
|
+
garanti -- voir la
|
|
85
|
+
[licence](https://github.com/MrFanghoDev/fletchtime/blob/master/LICENSE)
|
|
86
|
+
et le ton du
|
|
87
|
+
[guide de contribution](https://github.com/MrFanghoDev/fletchtime/blob/master/CONTRIBUTING.md)
|
|
88
|
+
pour ce que ça implique concrètement.
|
|
89
|
+
|
|
90
|
+
## Où chercher de l'aide
|
|
91
|
+
|
|
92
|
+
::::{grid} 2
|
|
93
|
+
:gutter: 3
|
|
94
|
+
|
|
95
|
+
:::{grid-item-card} 📖 Usage au quotidien
|
|
96
|
+
Réglages, pilotage d'un match : le manuel utilisateur intégré à
|
|
97
|
+
l'application, accessible depuis sa page d'accueil une fois installée.
|
|
98
|
+
:::
|
|
99
|
+
|
|
100
|
+
:::{grid-item-card} 🐛 Un bug, une question
|
|
101
|
+
Les [Issues GitHub](https://github.com/MrFanghoDev/fletchtime/issues).
|
|
102
|
+
:::
|
|
103
|
+
|
|
104
|
+
:::{grid-item-card} 🤝 Envie de contribuer
|
|
105
|
+
Code, documentation, idée -- voir
|
|
106
|
+
[CONTRIBUTING.md](https://github.com/MrFanghoDev/fletchtime/blob/master/CONTRIBUTING.md).
|
|
107
|
+
:::
|
|
108
|
+
|
|
109
|
+
:::{grid-item-card} 🔧 Fonctionnement technique
|
|
110
|
+
Le reste de cette documentation : {doc}`specifications`, {doc}`architecture`.
|
|
111
|
+
:::
|
|
112
|
+
|
|
113
|
+
::::
|
|
@@ -56,23 +56,69 @@ dépendent d'extensions compilées (Rust/C) peu fiables sur Pydroid 3, voir
|
|
|
56
56
|
(`pip install fletchtime` + commande `fletchtime`), exécutables
|
|
57
57
|
autoporteurs Windows/Linux, CI (lint + tests à chaque push).
|
|
58
58
|
|
|
59
|
-
##
|
|
59
|
+
## ✅ Étape 7 — Partage FFTL (hors contact fédération, démarche humaine)
|
|
60
60
|
|
|
61
61
|
- ~~Licence open source claire~~ -- déjà en place : `LICENSE`
|
|
62
62
|
(GPL-3.0-or-later), cohérent avec `pyproject.toml`.
|
|
63
63
|
- ~~Guide de contribution~~ -- fait : `CONTRIBUTING.md` (français/anglais),
|
|
64
64
|
flux classique fork/branche/Pull Request, volontairement simple.
|
|
65
|
-
- Nettoyage
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
- ~~Nettoyage~~ -- fait : balayage complet du dépôt, aucun contenu
|
|
66
|
+
spécifique au club trouvé en dehors de `web/assets/` (normal et
|
|
67
|
+
attendu là). Un vrai souci trouvé au passage, mais côté livraison
|
|
68
|
+
plutôt que dans le dépôt lui-même : un logo de club réel se
|
|
69
|
+
retrouvait dans les archives livrées malgré son exclusion de git --
|
|
70
|
+
corrigé.
|
|
71
|
+
- ~~Guide "premier club"~~ -- fait : {doc}`premier-club`, point d'entrée
|
|
72
|
+
pour quelqu'un qui découvre l'outil sans contexte préalable (c'est
|
|
73
|
+
quoi, pourquoi s'en servir, prérequis, premiers pas, peut-on lui faire
|
|
74
|
+
confiance, où chercher de l'aide).
|
|
71
75
|
- Contact fédération pour retour d'expérience / adoption éventuelle par
|
|
72
76
|
d'autres clubs -- démarche humaine, hors du champ du dépôt lui-même.
|
|
73
77
|
|
|
74
78
|
## Backlog — à discuter / non encore programmé dans une étape précise
|
|
75
79
|
|
|
80
|
+
- ~~**Icône de l'exécutable**~~ -- fait : `web/logo.ico` (multi-résolution,
|
|
81
|
+
16 à 256px, généré depuis `web/logo.svg`), utilisé par `fletchtime.spec`
|
|
82
|
+
pour l'exécutable Windows. Sans effet sous Linux, qui n'a pas ce
|
|
83
|
+
concept de métadonnées d'icône pour un simple binaire (vérifié). Le
|
|
84
|
+
favicon des pages web était en fait déjà en place, rien à faire là.
|
|
85
|
+
- ~~**Son dupliqué sur plusieurs onglets**~~ -- fait : l'aperçu de
|
|
86
|
+
`control.html` (une vraie instance de `display.html` en iframe) ne
|
|
87
|
+
joue plus jamais de son -- c'est une simple vue visuelle, pas un écran
|
|
88
|
+
destiné aux archers. `?mute=1` permet en plus de couper le son sur
|
|
89
|
+
n'importe quel autre onglet ouvert en trop sur le même PC.
|
|
90
|
+
- ~~**Chrono figé (sous Windows, pas lié au focus en réalité)**~~ --
|
|
91
|
+
cause la plus probable identifiée : sous Windows, remplacer un fichier
|
|
92
|
+
peut échouer avec une "violation de partage" si un autre processus l'a
|
|
93
|
+
ouvert au même instant (antivirus, surveillance de fichiers d'un
|
|
94
|
+
IDE...) -- observé une fois en pratique, précisément sur
|
|
95
|
+
`config/match_state.json`, écrit à chaque tick depuis la persistance
|
|
96
|
+
après plantage. Sans gestion d'erreur, ça tuait silencieusement
|
|
97
|
+
`tick_loop` pour de bon -- exactement le symptôme rapporté (gel
|
|
98
|
+
permanent, pas de déconnexion, se produisant aussi bien en GUI qu'en
|
|
99
|
+
headless, sans lien réel avec le focus). Deux correctifs : `tick_loop`
|
|
100
|
+
capture désormais toute exception imprévue en son sein (journalisée
|
|
101
|
+
avec sa trace complète, ne meurt plus silencieusement) et
|
|
102
|
+
`save_match_snapshot` retente quelques fois avant d'abandonner
|
|
103
|
+
proprement (jamais d'exception qui remonterait perturber la diffusion
|
|
104
|
+
de l'état aux écrans). Testé concrètement : échec transitoire récupéré,
|
|
105
|
+
échec permanent absorbé sans lever.
|
|
106
|
+
- ~~**Journal absent du widget de la fenêtre**~~ -- vrai bug trouvé en
|
|
107
|
+
creusant le point précédent : `configure_logging` était appelé sans
|
|
108
|
+
préciser `console_level`, retombant sur le défaut `WARNING` (pensé
|
|
109
|
+
pour un terminal silencieux) -- qui filtrait justement tout ce que ce
|
|
110
|
+
widget est censé montrer (commandes, connexions, transitions, toutes
|
|
111
|
+
en `INFO`). Corrigé avec `console_level=INFO` explicite pour la
|
|
112
|
+
fenêtre. Confirmé par comparaison directe avant/après : file vide
|
|
113
|
+
avant, message présent après.
|
|
114
|
+
- ~~**Données techniques dans la fenêtre**~~ -- fait : nouvel endpoint
|
|
115
|
+
`/api/status` (les mêmes données déjà affichées dans `control.html`),
|
|
116
|
+
affiché dans la fenêtre via sondage périodique. `ServerRuntime`
|
|
117
|
+
construit maintenant le `MatchServer` une seule fois et le partage
|
|
118
|
+
entre les deux serveurs, plutôt que d'en laisser `run_ws_server` créer
|
|
119
|
+
un nouveau à chaque démarrage.
|
|
120
|
+
|
|
121
|
+
|
|
76
122
|
- ~~**Remerciements**~~ -- structure prête, à compléter : `REMERCIEMENTS.md`
|
|
77
123
|
(modèle à remplir avec les noms), lié depuis le README et intégré à la
|
|
78
124
|
doc Sphinx ({doc}`remerciements` -- même contenu, pas dupliqué, via une
|
|
@@ -71,6 +71,13 @@ exe = EXE(
|
|
|
71
71
|
# assets/ du bootstrap), d'où un serveur qui tourne mais ne sert rien
|
|
72
72
|
# d'utile. "." restaure explicitement l'ancien comportement.
|
|
73
73
|
contents_directory=".",
|
|
74
|
+
# Icône de l'exécutable (barre des tâches, explorateur de fichiers,
|
|
75
|
+
# raccourci) -- fichier .ico multi-résolution (16 à 256px), généré à
|
|
76
|
+
# partir de web/logo.svg. PyInstaller ignore ce paramètre sous Linux
|
|
77
|
+
# (les .ico n'y ont pas de sens -- l'icône affichée dépend du
|
|
78
|
+
# gestionnaire de fichiers/bureau, hors du contrôle de l'exécutable
|
|
79
|
+
# lui-même), donc rien à prévoir de spécial pour cette plateforme.
|
|
80
|
+
icon=str(project_root / "src" / "fletchtime" / "web" / "logo.ico"),
|
|
74
81
|
)
|
|
75
82
|
|
|
76
83
|
coll = COLLECT(
|
|
@@ -19,6 +19,8 @@ Deux notions de dossier bien distinctes ici, à ne pas confondre :
|
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
|
+
import argparse
|
|
23
|
+
import logging
|
|
22
24
|
import shutil
|
|
23
25
|
import signal
|
|
24
26
|
import socket
|
|
@@ -150,7 +152,85 @@ def _print_banner(ip: str, data_root: Path, http_port: int) -> None:
|
|
|
150
152
|
print("=" * 60)
|
|
151
153
|
|
|
152
154
|
|
|
153
|
-
def
|
|
155
|
+
def _build_arg_parser() -> argparse.ArgumentParser:
|
|
156
|
+
parser = argparse.ArgumentParser(
|
|
157
|
+
prog="fletchtime",
|
|
158
|
+
description=(
|
|
159
|
+
"Serveur de chronométrage pour compétitions d'archerie FFTL " "(Indoor et Flint)."
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
parser.add_argument(
|
|
163
|
+
"-V",
|
|
164
|
+
"--version",
|
|
165
|
+
action="version",
|
|
166
|
+
version=f"FletchTime {__version__}",
|
|
167
|
+
)
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
"--headless",
|
|
170
|
+
"--no-gui",
|
|
171
|
+
dest="headless",
|
|
172
|
+
action="store_true",
|
|
173
|
+
help="Mode terminal, sans fenêtre graphique.",
|
|
174
|
+
)
|
|
175
|
+
parser.add_argument(
|
|
176
|
+
"-v",
|
|
177
|
+
"--verbose",
|
|
178
|
+
action="store_true",
|
|
179
|
+
help="Affiche les journaux applicatifs (commandes reçues, "
|
|
180
|
+
"(dé)connexions...) dans le terminal, pas seulement dans le "
|
|
181
|
+
"fichier de journal.",
|
|
182
|
+
)
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
"-d",
|
|
185
|
+
"--debug",
|
|
186
|
+
action="store_true",
|
|
187
|
+
help="Journalisation la plus détaillée possible, fichier compris " "-- implique --verbose.",
|
|
188
|
+
)
|
|
189
|
+
parser.add_argument(
|
|
190
|
+
"--http-port",
|
|
191
|
+
type=int,
|
|
192
|
+
metavar="PORT",
|
|
193
|
+
help="Port HTTP -- remplace la valeur de config/gui.toml pour "
|
|
194
|
+
"cette exécution seulement, sans la modifier (voir aussi la "
|
|
195
|
+
"fenêtre graphique pour un réglage persistant).",
|
|
196
|
+
)
|
|
197
|
+
parser.add_argument(
|
|
198
|
+
"--ws-port",
|
|
199
|
+
type=int,
|
|
200
|
+
metavar="PORT",
|
|
201
|
+
help="Port WebSocket -- remplace la valeur de config/gui.toml "
|
|
202
|
+
"pour cette exécution seulement, sans la modifier.",
|
|
203
|
+
)
|
|
204
|
+
return parser
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _resolve_console_log_level(args: argparse.Namespace) -> int:
|
|
208
|
+
if args.debug:
|
|
209
|
+
return logging.DEBUG
|
|
210
|
+
if args.verbose:
|
|
211
|
+
return logging.INFO
|
|
212
|
+
return logging.WARNING
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _resolve_ports(args: argparse.Namespace, parser: argparse.ArgumentParser) -> tuple[int, int]:
|
|
216
|
+
"""Priorité aux options de la ligne de commande sur config/gui.toml,
|
|
217
|
+
sans jamais modifier ce fichier -- une exécution scriptée/CI ne doit
|
|
218
|
+
pas laisser de trace persistante par accident. Revalide les deux
|
|
219
|
+
ports ensemble (même règle que config_store.save_gui_config) même
|
|
220
|
+
si un seul des deux vient de la ligne de commande, pour ne jamais se
|
|
221
|
+
retrouver avec une combinaison invalide (identiques, hors bornes)."""
|
|
222
|
+
gui_config = config_store.load_gui_config()
|
|
223
|
+
http_port = args.http_port if args.http_port is not None else gui_config["http_port"]
|
|
224
|
+
ws_port = args.ws_port if args.ws_port is not None else gui_config["ws_port"]
|
|
225
|
+
for name, port in (("--http-port", http_port), ("--ws-port", ws_port)):
|
|
226
|
+
if not (1 <= port <= 65535):
|
|
227
|
+
parser.error(f"{name} : le port doit être entre 1 et 65535 (reçu {port}).")
|
|
228
|
+
if http_port == ws_port:
|
|
229
|
+
parser.error("--http-port et --ws-port doivent être différents.")
|
|
230
|
+
return http_port, ws_port
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _run_headless(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None:
|
|
154
234
|
"""Mode terminal classique -- utilisé si l'interface graphique n'a pas
|
|
155
235
|
pu être chargée (ex. `customtkinter` absent), ou explicitement demandé
|
|
156
236
|
via `--headless`/`--no-gui`."""
|
|
@@ -159,18 +239,18 @@ def _run_headless() -> None:
|
|
|
159
239
|
ensure_directories(data_root, app_web_dir)
|
|
160
240
|
assets_dir = data_root / "web" / "assets"
|
|
161
241
|
|
|
162
|
-
|
|
242
|
+
console_level = _resolve_console_log_level(args)
|
|
243
|
+
file_level = logging.DEBUG if args.debug else logging.INFO
|
|
244
|
+
log_file = configure_logging(data_root / "logs", console_level, file_level)
|
|
163
245
|
print(f"Journal détaillé / Detailed log: {log_file}")
|
|
164
246
|
|
|
165
|
-
# Ports lus depuis config/gui.toml (mêmes préférences que
|
|
166
|
-
# graphique, voir config_store.load_gui_config)
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
http_port = gui_config["http_port"]
|
|
173
|
-
ws_port = gui_config["ws_port"]
|
|
247
|
+
# Ports lus depuis config/gui.toml par défaut (mêmes préférences que
|
|
248
|
+
# la fenêtre graphique, voir config_store.load_gui_config), sauf
|
|
249
|
+
# remplacement explicite via --http-port/--ws-port pour cette seule
|
|
250
|
+
# exécution -- permet de faire tourner plusieurs salles de
|
|
251
|
+
# compétition sur un même PC sans dossier séparé par salle, utile
|
|
252
|
+
# pour un lancement scripté/CI par exemple.
|
|
253
|
+
http_port, ws_port = _resolve_ports(args, parser)
|
|
174
254
|
|
|
175
255
|
_print_banner(local_ip(), data_root, http_port)
|
|
176
256
|
|
|
@@ -193,8 +273,11 @@ def _run_headless() -> None:
|
|
|
193
273
|
|
|
194
274
|
|
|
195
275
|
def main() -> None:
|
|
196
|
-
|
|
197
|
-
|
|
276
|
+
parser = _build_arg_parser()
|
|
277
|
+
args = parser.parse_args()
|
|
278
|
+
|
|
279
|
+
if args.headless:
|
|
280
|
+
_run_headless(args, parser)
|
|
198
281
|
return
|
|
199
282
|
|
|
200
283
|
try:
|
|
@@ -212,7 +295,7 @@ def main() -> None:
|
|
|
212
295
|
# repli ne risque pas un conflit de port avec _run_headless().
|
|
213
296
|
print(f"Interface graphique indisponible ({exc}) -- mode terminal.")
|
|
214
297
|
print(f"Graphical interface unavailable ({exc}) -- terminal mode.")
|
|
215
|
-
_run_headless()
|
|
298
|
+
_run_headless(args, parser)
|
|
216
299
|
|
|
217
300
|
|
|
218
301
|
if __name__ == "__main__":
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 5)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'ga6e6ad564'
|