labfy-investigation/docs/tickets/closed/TICKET-035.md
2026-07-17 14:35:21 +02:00

790 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Ticket #035 — File de tâches et panneau dactivité
## Contexte
Le ticket #034 a introduit `BackgroundTask`, une primitive asynchrone capable de :
- lancer un worker dans un thread GLib ;
- suivre son état ;
- signaler sa progression ;
- gérer lannulation ;
- conserver un résultat ou une erreur ;
- revenir sur le contexte principal à la fin ;
- rester vivante grâce au comptage de références.
Cette primitive ne gère cependant pas encore plusieurs tâches ni leur présentation dans linterface.
## Objectif
Créer un gestionnaire opaque :
```c
TaskManager
```
chargé de suivre les tâches actives et terminées, puis ajouter un panneau GTK permettant de les consulter.
Le ticket doit permettre de voir :
```text
Titre
État
Progression
Message courant
Bouton Annuler
```
Le gestionnaire reste indépendant de GTK.
---
# Architecture attendue
Créer :
```text
include/core/task_manager.h
src/core/task_manager.c
tests/test_task_manager.c
include/widgets/task_panel.h
src/widgets/task_panel.c
```
Relations :
```text
Application
├── possède TaskManager
└── transmet TaskManager à MainWindow
MainWindow
└── possède TaskPanel
TaskPanel
└── observe TaskManager
TaskManager
└── conserve des références vers BackgroundTask
```
---
# Phase A — `TaskManager`
## 1. Type opaque
Dans :
```text
include/core/task_manager.h
```
déclarer :
```c
typedef struct TaskManager TaskManager;
```
Le type doit rester indépendant de GTK, SQLite et des enquêtes.
---
## 2. Callback de changement
Définir :
```c
typedef void (*TaskManagerChangedCallback)(
TaskManager *task_manager,
gpointer user_data
);
```
Le callback signale quun changement visible a eu lieu :
- tâche ajoutée ;
- progression modifiée ;
- tâche terminée ;
- tâche retirée ;
- annulation demandée.
Le callback est une notification globale. Il ne transmet pas directement une tâche particulière.
---
## 3. API publique
### Construction
```c
TaskManager *task_manager_new(void);
void task_manager_free(
TaskManager *task_manager
);
```
### Ajouter une tâche
```c
gboolean task_manager_add(
TaskManager *task_manager,
BackgroundTask *task,
GError **error
);
```
Règles :
- `task_manager` devient propriétaire dune référence supplémentaire ;
- lappelant conserve sa propre référence ;
- une même tâche ne peut pas être ajoutée deux fois ;
- une tâche déjà terminée peut être ajoutée, mais elle est immédiatement visible comme terminée ;
- `NULL` est refusé.
### Consulter les tâches
```c
gsize task_manager_get_count(
const TaskManager *task_manager
);
BackgroundTask *task_manager_get_task(
const TaskManager *task_manager,
gsize index
);
```
`task_manager_get_task()` retourne une nouvelle référence que lappelant doit libérer.
### Retirer une tâche
```c
gboolean task_manager_remove(
TaskManager *task_manager,
BackgroundTask *task
);
```
La tâche nest pas annulée automatiquement.
### Supprimer les tâches terminées
```c
gsize task_manager_remove_finished(
TaskManager *task_manager
);
```
États concernés :
```text
COMPLETED
FAILED
CANCELLED
```
### Annuler toutes les tâches actives
```c
void task_manager_cancel_all(
TaskManager *task_manager
);
```
### Callback de changement
```c
void task_manager_set_changed_callback(
TaskManager *task_manager,
TaskManagerChangedCallback callback,
gpointer user_data,
GDestroyNotify user_data_destroy
);
```
Le manager possède `user_data` après lappel.
Remplacer le callback existant doit détruire les anciennes données exactement une fois.
---
## 4. Domaine derreur
Définir :
```c
typedef enum
{
TASK_MANAGER_ERROR_INVALID_ARGUMENT,
TASK_MANAGER_ERROR_ALREADY_ADDED
} TaskManagerError;
```
Puis :
```c
#define TASK_MANAGER_ERROR \
task_manager_error_quark()
GQuark task_manager_error_quark(void);
```
---
## 5. Structure interne recommandée
```c
struct TaskManager
{
GMutex mutex;
GPtrArray *tasks;
TaskManagerChangedCallback changed_callback;
gpointer changed_user_data;
GDestroyNotify changed_user_data_destroy;
};
```
`tasks` doit contenir des références `BackgroundTask *`.
Configurer le `GPtrArray` avec :
```c
background_task_unref
```
comme fonction de destruction.
---
## 6. Notification périodique
`BackgroundTask` ne possède pas encore de callback de progression.
Pour ce ticket, `TaskPanel` peut rafraîchir périodiquement laffichage avec :
```c
g_timeout_add()
```
fréquence recommandée :
```text
200 à 300 ms
```
`TaskManager` notifie immédiatement les changements structurels.
La progression sera relue par le panneau.
Une API dobservation plus fine pourra être ajoutée plus tard si nécessaire.
---
# Phase B — Tests de `TaskManager`
Créer :
```text
tests/test_task_manager.c
```
## Tests minimaux
### Construction
Vérifier :
```text
manager non NULL
compteur initial à zéro
```
### Ajout
Ajouter une tâche et vérifier :
```text
compteur à un
callback déclenché
tâche récupérable
référence indépendante
```
### Doublon
Ajouter deux fois la même tâche :
```text
FALSE
TASK_MANAGER_ERROR_ALREADY_ADDED
compteur inchangé
```
### Retrait
Retirer une tâche :
```text
TRUE
compteur décrémenté
callback déclenché
```
### Retrait inconnu
Retirer une tâche absente :
```text
FALSE
aucun crash
```
### Nettoyage des tâches terminées
Ajouter :
- une tâche en attente ;
- une tâche terminée ;
- une tâche échouée ;
- une tâche annulée.
Vérifier que seules les tâches terminées sont supprimées.
### Annulation globale
Ajouter plusieurs tâches actives et appeler :
```c
task_manager_cancel_all()
```
Vérifier que chaque `GCancellable` reçoit une demande dannulation.
### Durée de vie
Vérifier que :
- le manager conserve ses références ;
- la destruction du manager libère toutes les tâches ;
- le remplacement du callback détruit les anciennes données une seule fois.
---
# Phase C — `TaskPanel`
## 7. Type opaque
Dans :
```text
include/widgets/task_panel.h
```
déclarer :
```c
typedef struct TaskPanel TaskPanel;
```
API :
```c
TaskPanel *task_panel_new(
TaskManager *task_manager
);
GtkWidget *task_panel_get_widget(
const TaskPanel *task_panel
);
void task_panel_refresh(
TaskPanel *task_panel
);
void task_panel_free(
TaskPanel *task_panel
);
```
`TaskPanel` ne devient pas propriétaire de `TaskManager`.
`TaskPanel` doit rester valide tant que le manager existe.
---
## 8. Interface recommandée
Premier rendu simple :
```text
┌──────────────────────────────────────────────────────────┐
│ Activité [ Nettoyer ] │
├──────────────────────────────────────────────────────────┤
│ Extraction des métadonnées │
│ En cours — 45 % │
│ [████████░░░░░░░░░░] [ Annuler ] │
├──────────────────────────────────────────────────────────┤
│ Calcul SHA-256 │
│ Terminé │
│ [████████████████████] │
└──────────────────────────────────────────────────────────┘
```
Widgets GTK possibles :
- `GtkBox` ;
- `GtkLabel` ;
- `GtkProgressBar` ;
- `GtkButton` ;
- `GtkScrolledWindow`.
Ne pas utiliser encore de `GtkListView` si cela complexifie inutilement le ticket.
---
## 9. État visuel
Créer une fonction privée traduisant les états :
```text
PENDING → En attente
RUNNING → En cours
COMPLETED → Terminée
FAILED → Échouée
CANCELLED → Annulée
```
Une tâche en erreur doit afficher son message derreur sous forme courte.
Une tâche en cours doit afficher son message de progression lorsquil existe.
---
## 10. Bouton dannulation
Le bouton `Annuler` doit être visible uniquement pour :
```text
RUNNING
```
Son callback appelle :
```c
background_task_cancel(task);
```
Le bouton ne doit pas retirer la tâche.
---
## 11. Bouton de nettoyage
Ajouter :
```text
Nettoyer
```
Il appelle :
```c
task_manager_remove_finished()
```
Les tâches actives restent visibles.
---
## 12. Rafraîchissement périodique
`TaskPanel` doit enregistrer une source GLib :
```c
g_timeout_add()
```
Elle appelle :
```c
task_panel_refresh()
```
Lors de `task_panel_free()` :
- retirer la source avec `g_source_remove()` ;
- empêcher tout callback après destruction ;
- ne pas détruire `TaskManager`.
---
# Phase D — Intégration GTK
## 13. Ajouter le panneau à `MainWindow`
Modifier :
```text
include/views/main_window.h
src/views/main_window.c
```
Changer la construction :
```c
MainWindow *main_window_new(
GtkApplication *application,
TaskManager *task_manager
);
```
`MainWindow` doit créer :
```c
TaskPanel *task_panel;
```
Le panneau peut être placé :
- sous la zone de travail ;
- dans un volet inférieur ;
- ou temporairement dans une colonne latérale secondaire.
Pour ce ticket, un volet inférieur sous `GtkPaned` est acceptable.
---
## 14. Ajouter `TaskManager` à `Application`
Dans la structure privée :
```c
TaskManager *task_manager;
```
Dans `application_new()` :
```c
application->task_manager =
task_manager_new();
```
En cas déchec, nettoyer lapplication.
Dans `application_on_activate()` :
```c
application->main_window = main_window_new(
gtk_application,
application->task_manager
);
```
Dans `application_free()` :
1. fermer/détruire `MainWindow` ;
2. libérer `TaskManager` après le panneau ;
3. poursuivre le nettoyage existant.
Lordre doit éviter que `TaskPanel` lise un manager déjà détruit.
---
# Phase E — Tâche de démonstration
## 15. Ajouter temporairement une tâche de test
Pour valider linterface, ajouter un bouton temporaire :
```text
Tâche de test
```
Il lance une `BackgroundTask` qui :
- dure environ deux secondes ;
- progresse de 0 à 100 % ;
- accepte lannulation ;
- retourne un résultat simple.
Cette tâche doit être ajoutée au `TaskManager`.
Le bouton pourra être supprimé lorsque le premier véritable traitement asynchrone sera disponible.
Le code de démonstration doit rester clairement identifié :
```c
/* Temporary demonstration task for ticket #035. */
```
---
# Hors périmètre
Ne pas ajouter encore :
- de limite de concurrence ;
- de priorité ;
- de persistance SQLite ;
- de reprise après redémarrage ;
- dhistorique permanent ;
- dexécution de commandes ;
- de recherche DNS ;
- dExifTool ;
- de notifications système ;
- de tri avancé ;
- de pagination.
---
# Critères dacceptation
- [ ] `TaskManager` est opaque.
- [ ] `TaskManager` ne dépend pas de GTK.
- [ ] `TaskManager` protège sa collection avec `GMutex`.
- [ ] Le manager conserve une référence par tâche.
- [ ] Une tâche ne peut pas être ajoutée deux fois.
- [ ] Une tâche peut être retirée.
- [ ] Les tâches terminées peuvent être nettoyées.
- [ ] Toutes les tâches actives peuvent être annulées.
- [ ] Le callback de changement fonctionne.
- [ ] `TaskPanel` affiche toutes les tâches.
- [ ] La progression est visible.
- [ ] Létat est lisible.
- [ ] Une tâche active peut être annulée.
- [ ] Les tâches terminées peuvent être supprimées.
- [ ] Le rafraîchissement périodique est correctement détruit.
- [ ] `MainWindow` ne devient pas propriétaire du manager.
- [ ] Lordre de destruction ne provoque aucun crash.
- [ ] La tâche de démonstration fonctionne.
- [ ] `make` réussit.
- [ ] `make test` réussit.
- [ ] `git diff --check` ne retourne aucune erreur.
---
# Audit attendu
Vérifier lindépendance du manager :
```bash
rg -n \
'#include <gtk|sqlite3_|Database|Investigation' \
include/core/task_manager.h \
src/core/task_manager.c
```
Résultat attendu :
```text
aucune sortie
```
Vérifier les références :
```bash
rg -n \
'background_task_ref|background_task_unref' \
src/core/task_manager.c
```
Vérifier le rafraîchissement GTK :
```bash
rg -n \
'g_timeout_add|g_source_remove|task_panel_refresh' \
src/widgets/task_panel.c
```
Vérifier labsence de threads bruts :
```bash
rg -n \
'pthread_|pthread.h' \
include/core/task_manager.h \
src/core/task_manager.c \
src/widgets/task_panel.c
```
Résultat attendu :
```text
aucune sortie
```
---
# Fichiers concernés
```text
include/core/task_manager.h
src/core/task_manager.c
tests/test_task_manager.c
include/widgets/task_panel.h
src/widgets/task_panel.c
include/views/main_window.h
src/views/main_window.c
src/core/application.c
Makefile
```
---
# Commit attendu
```bash
make clean
make
make test
git diff --check
git status --short
```
```bash
git add \
include/core/task_manager.h \
src/core/task_manager.c \
tests/test_task_manager.c \
include/widgets/task_panel.h \
src/widgets/task_panel.c \
include/views/main_window.h \
src/views/main_window.c \
src/core/application.c \
Makefile
```
```bash
git diff --cached --stat
git diff --cached
```
```bash
git commit -m "feat(ui): add task manager and activity panel"
git push
```
---
# Résultat attendu
Après ce ticket, Labfy possédera une infrastructure visible pour tous les futurs traitements longs :
```text
Utilisateur
Action GTK
BackgroundTask
TaskManager
TaskPanel
```
Le ticket suivant pourra ajouter le registre des dépendances et lancer les premiers contrôles doutils externes sans bloquer linterface.