14 KiB
Ticket #035 — File de tâches et panneau d’activité
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 l’annulation ;
- 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 l’interface.
Objectif
Créer un gestionnaire opaque :
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 :
Titre
État
Progression
Message courant
Bouton Annuler
Le gestionnaire reste indépendant de GTK.
Architecture attendue
Créer :
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 :
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 :
include/core/task_manager.h
déclarer :
typedef struct TaskManager TaskManager;
Le type doit rester indépendant de GTK, SQLite et des enquêtes.
2. Callback de changement
Définir :
typedef void (*TaskManagerChangedCallback)(
TaskManager *task_manager,
gpointer user_data
);
Le callback signale qu’un 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
TaskManager *task_manager_new(void);
void task_manager_free(
TaskManager *task_manager
);
Ajouter une tâche
gboolean task_manager_add(
TaskManager *task_manager,
BackgroundTask *task,
GError **error
);
Règles :
task_managerdevient propriétaire d’une référence supplémentaire ;- l’appelant 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 ;
NULLest refusé.
Consulter les tâches
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 l’appelant doit libérer.
Retirer une tâche
gboolean task_manager_remove(
TaskManager *task_manager,
BackgroundTask *task
);
La tâche n’est pas annulée automatiquement.
Supprimer les tâches terminées
gsize task_manager_remove_finished(
TaskManager *task_manager
);
États concernés :
COMPLETED
FAILED
CANCELLED
Annuler toutes les tâches actives
void task_manager_cancel_all(
TaskManager *task_manager
);
Callback de changement
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 l’appel.
Remplacer le callback existant doit détruire les anciennes données exactement une fois.
4. Domaine d’erreur
Définir :
typedef enum
{
TASK_MANAGER_ERROR_INVALID_ARGUMENT,
TASK_MANAGER_ERROR_ALREADY_ADDED
} TaskManagerError;
Puis :
#define TASK_MANAGER_ERROR \
task_manager_error_quark()
GQuark task_manager_error_quark(void);
5. Structure interne recommandée
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 :
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 l’affichage avec :
g_timeout_add()
fréquence recommandée :
200 à 300 ms
TaskManager notifie immédiatement les changements structurels.
La progression sera relue par le panneau.
Une API d’observation plus fine pourra être ajoutée plus tard si nécessaire.
Phase B — Tests de TaskManager
Créer :
tests/test_task_manager.c
Tests minimaux
Construction
Vérifier :
manager non NULL
compteur initial à zéro
Ajout
Ajouter une tâche et vérifier :
compteur à un
callback déclenché
tâche récupérable
référence indépendante
Doublon
Ajouter deux fois la même tâche :
FALSE
TASK_MANAGER_ERROR_ALREADY_ADDED
compteur inchangé
Retrait
Retirer une tâche :
TRUE
compteur décrémenté
callback déclenché
Retrait inconnu
Retirer une tâche absente :
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 :
task_manager_cancel_all()
Vérifier que chaque GCancellable reçoit une demande d’annulation.
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 :
include/widgets/task_panel.h
déclarer :
typedef struct TaskPanel TaskPanel;
API :
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 :
┌──────────────────────────────────────────────────────────┐
│ 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 :
PENDING → En attente
RUNNING → En cours
COMPLETED → Terminée
FAILED → Échouée
CANCELLED → Annulée
Une tâche en erreur doit afficher son message d’erreur sous forme courte.
Une tâche en cours doit afficher son message de progression lorsqu’il existe.
10. Bouton d’annulation
Le bouton Annuler doit être visible uniquement pour :
RUNNING
Son callback appelle :
background_task_cancel(task);
Le bouton ne doit pas retirer la tâche.
11. Bouton de nettoyage
Ajouter :
Nettoyer
Il appelle :
task_manager_remove_finished()
Les tâches actives restent visibles.
12. Rafraîchissement périodique
TaskPanel doit enregistrer une source GLib :
g_timeout_add()
Elle appelle :
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 :
include/views/main_window.h
src/views/main_window.c
Changer la construction :
MainWindow *main_window_new(
GtkApplication *application,
TaskManager *task_manager
);
MainWindow doit créer :
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 :
TaskManager *task_manager;
Dans application_new() :
application->task_manager =
task_manager_new();
En cas d’échec, nettoyer l’application.
Dans application_on_activate() :
application->main_window = main_window_new(
gtk_application,
application->task_manager
);
Dans application_free() :
- fermer/détruire
MainWindow; - libérer
TaskManageraprès le panneau ; - poursuivre le nettoyage existant.
L’ordre 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 l’interface, ajouter un bouton temporaire :
Tâche de test
Il lance une BackgroundTask qui :
- dure environ deux secondes ;
- progresse de 0 à 100 % ;
- accepte l’annulation ;
- 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é :
/* 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 ;
- d’historique permanent ;
- d’exécution de commandes ;
- de recherche DNS ;
- d’ExifTool ;
- de notifications système ;
- de tri avancé ;
- de pagination.
Critères d’acceptation
TaskManagerest opaque.TaskManagerne dépend pas de GTK.TaskManagerprotège sa collection avecGMutex.- 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.
TaskPanelaffiche 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.
MainWindowne devient pas propriétaire du manager.- L’ordre de destruction ne provoque aucun crash.
- La tâche de démonstration fonctionne.
makeréussit.make testréussit.git diff --checkne retourne aucune erreur.
Audit attendu
Vérifier l’indépendance du manager :
rg -n \
'#include <gtk|sqlite3_|Database|Investigation' \
include/core/task_manager.h \
src/core/task_manager.c
Résultat attendu :
aucune sortie
Vérifier les références :
rg -n \
'background_task_ref|background_task_unref' \
src/core/task_manager.c
Vérifier le rafraîchissement GTK :
rg -n \
'g_timeout_add|g_source_remove|task_panel_refresh' \
src/widgets/task_panel.c
Vérifier l’absence de threads bruts :
rg -n \
'pthread_|pthread.h' \
include/core/task_manager.h \
src/core/task_manager.c \
src/widgets/task_panel.c
Résultat attendu :
aucune sortie
Fichiers concernés
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
make clean
make
make test
git diff --check
git status --short
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
git diff --cached --stat
git diff --cached
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 :
Utilisateur
↓
Action GTK
↓
BackgroundTask
↓
TaskManager
↓
TaskPanel
Le ticket suivant pourra ajouter le registre des dépendances et lancer les premiers contrôles d’outils externes sans bloquer l’interface.