Intégrer le chargement asynchrone du graphe dans la fenêtre principale #61

Closed
opened 2026-07-21 09:43:59 +02:00 by fy59 · 0 comments
Owner

Intégrer le chargement asynchrone du graphe dans la fenêtre principale

Contexte

Le projet possède désormais :

  • InvestigationGraphModel ;
  • InvestigationGraphLoader ;
  • InvestigationGraphLoadTask ;
  • une fenêtre principale GTK ;
  • un mécanisme d'ouverture d'enquête existant ;
  • un workspace destiné à accueillir les futures vues de l'enquête.

Le chargement du graphe est désormais capable de s'exécuter dans un worker avec
sa propre connexion SQLite et de restituer le résultat sur le thread principal.

Cette fonctionnalité doit maintenant être intégrée au cycle de vie réel de
l'application.

Objectif

Lorsqu'une enquête est ouverte dans la fenêtre principale :

  1. récupérer le chemin de sa base SQLite ;
  2. démarrer InvestigationGraphLoadTask ;
  3. afficher un état de chargement non bloquant ;
  4. recevoir le InvestigationGraphModel sur le thread GTK ;
  5. conserver ce graphe dans le contexte de la fenêtre ou de l'enquête active ;
  6. remettre le graphe au workspace ;
  7. gérer les erreurs sans faire planter l'application ;
  8. annuler proprement la tâche lors d'une fermeture ou d'un changement
    d'enquête.

Ce ticket ne dessine pas encore le graphe.

Architecture attendue

Le cycle de vie doit être explicite :

ouverture de l'enquête
    ↓
création InvestigationGraphLoadTask
    ↓
affichage état "chargement"
    ↓
worker SQLite
    ↓
callback sur thread GTK
    ├── succès → stockage du graphe + mise à jour workspace
    └── erreur → affichage erreur + état vide

La fenêtre ne doit jamais appeler directement
investigation_graph_loader_load().

Propriété du graphe

Le propriétaire du graphe chargé doit être clairement défini.

Architecture recommandée :

MainWindow / contexte d'enquête active
    possède InvestigationGraphModel
Workspace
    emprunte InvestigationGraphModel

Règles :

  • le callback reçoit la propriété du graphe ;
  • l'objet propriétaire remplace éventuellement l'ancien graphe ;
  • l'ancien graphe est libéré avant ou au moment du remplacement ;
  • le workspace ne libère jamais le graphe emprunté ;
  • la fermeture de l'enquête libère le graphe ;
  • la fermeture de la fenêtre libère le graphe ;
  • aucun pointeur vers un ancien graphe ne doit rester dans les widgets.

État privé à ajouter

Le contexte propriétaire doit conserver au minimum :

InvestigationGraphLoadTask *graph_load_task;
InvestigationGraphModel *graph_model;
guint64 graph_load_generation;

graph_load_generation permet d'ignorer un résultat devenu obsolète lorsqu'une
autre enquête a été ouverte avant la fin du chargement précédent.

Une autre stratégie équivalente est acceptée si elle garantit le même résultat.

Générations de chargement

Chaque nouvelle ouverture d'enquête doit :

  1. incrémenter un compteur de génération ;
  2. annuler la tâche précédente ;
  3. créer une nouvelle tâche ;
  4. transmettre la génération courante dans les données du callback.

Dans le callback :

génération reçue != génération actuelle
    ↓
résultat obsolète
    ↓
libération immédiate du graphe reçu
    ↓
aucune modification de l'interface

Même si l'annulation est demandée, cette vérification reste obligatoire car une
ancienne tâche peut être en phase de finalisation.

État de chargement dans le workspace

Ajouter une API explicite au workspace, adaptée à son architecture actuelle.

Exemple possible :

void workspace_set_graph_loading(
    Workspace *workspace
);

void workspace_set_graph(
    Workspace *workspace,
    const InvestigationGraphModel *graph_model
);

void workspace_set_graph_error(
    Workspace *workspace,
    const char *message
);

void workspace_clear_graph(
    Workspace *workspace
);

Les noms exacts peuvent être adaptés aux conventions actuelles.

Chargement

Pendant le chargement, afficher au minimum :

Chargement de l'enquête…

Un GtkSpinner peut être utilisé.

Succès

À ce stade, le workspace peut afficher un résumé temporaire :

Graphe chargé
3 entités
5 relations

Ce résumé sert uniquement à valider l'intégration avant le futur canvas.

Erreur

Afficher un message compréhensible :

Impossible de charger le graphe de l'enquête.

Le détail technique de GError peut être :

  • journalisé ;
  • affiché dans une zone secondaire ;
  • ou intégré dans le message si cela reste lisible.

Ne pas afficher seulement un message SQLite brut sans contexte.

Démarrage du chargement

Créer une fonction interne dédiée, par exemple :

static void main_window_start_graph_loading(
    MainWindow *main_window,
    const char *database_path
);

Elle doit :

  • vérifier les arguments ;
  • annuler et libérer l'ancienne tâche ;
  • libérer ou détacher l'ancien graphe ;
  • mettre le workspace en état de chargement ;
  • créer la nouvelle tâche ;
  • lancer la tâche ;
  • gérer un échec immédiat de création ou de démarrage.

Ne pas disperser cette logique dans plusieurs callbacks GTK.

Callback de fin

Créer une fonction interne dédiée :

static void main_window_on_graph_loaded(
    InvestigationGraphLoadTask *load_task,
    InvestigationGraphModel *graph_model,
    const GError *error,
    gpointer user_data
);

Elle doit :

  • être exécutée sur le thread GTK ;
  • vérifier la génération ;
  • ne jamais conserver simultanément graph_model et error ;
  • transférer le graphe au propriétaire en cas de succès ;
  • mettre à jour le workspace ;
  • libérer un résultat obsolète ;
  • ne pas libérer l'erreur empruntée ;
  • ne pas détruire directement la tâche depuis un état incohérent.

Données du callback

Créer une petite structure possédée par la tâche :

typedef struct
{
    MainWindow *main_window;
    guint64 generation;
} MainWindowGraphLoadContext;

Ou une variante adaptée au type réel de la fenêtre.

Son destructeur doit :

  • libérer uniquement les ressources qu'elle possède ;
  • ne pas détruire la fenêtre GTK ;
  • ne pas provoquer d'accès après destruction.

Si la fenêtre n'est pas un objet GObject possédé par référence, prévoir un
mécanisme sûr pour rendre le callback inactif après fermeture.

Fermeture de la fenêtre

Lors de la destruction de la fenêtre :

  1. incrémenter ou invalider la génération ;
  2. annuler la tâche active ;
  3. libérer la tâche ;
  4. détacher le graphe du workspace ;
  5. libérer le graphe possédé ;
  6. ne permettre aucun callback utilisateur ultérieur.

Le cycle de vie garanti par InvestigationGraphLoadTask doit être utilisé, pas
contourné.

Changement d'enquête

Si une seconde enquête est ouverte pendant le chargement de la première :

  • la première tâche est annulée ;
  • son résultat ne doit jamais remplacer la seconde enquête ;
  • le workspace affiche l'état de la seconde enquête ;
  • le graphe de la première est libéré s'il arrive tardivement ;
  • seule la génération courante peut modifier l'interface.

Fermeture d'une enquête active

Lorsqu'aucune enquête n'est active :

  • annuler le chargement courant ;
  • libérer le graphe courant ;
  • remettre le workspace dans son état initial ;
  • ne conserver aucun chemin SQLite obsolète.

Erreurs immédiates

Les cas suivants doivent être gérés :

  • chemin SQLite absent ;
  • allocation de InvestigationGraphLoadTask impossible ;
  • démarrage de la tâche refusé ;
  • tâche déjà active à cause d'une incohérence interne.

Dans ces cas :

  • remettre le workspace en état d'erreur ;
  • journaliser le détail ;
  • libérer toutes les ressources déjà créées ;
  • ne pas laisser graph_load_task pointer vers un objet inutilisable.

Journalisation

Journaliser au minimum :

Début

Chargement asynchrone du graphe : <chemin>

Succès

Graphe chargé : N entités, M relations

Annulation

Chargement du graphe annulé

Erreur

Échec du chargement du graphe : <message>

Ne jamais journaliser de données sensibles issues des entités.

Tests

Créer ou compléter les tests des composants concernés.

Tests du workspace

Tester :

  1. état initial ;
  2. état de chargement ;
  3. affichage du spinner ;
  4. état de succès ;
  5. affichage du nombre d'entités ;
  6. affichage du nombre de relations ;
  7. état d'erreur ;
  8. nettoyage du message précédent ;
  9. retour à l'état vide ;
  10. le workspace ne libère pas le graphe emprunté.

Tests de l'intégration fenêtre

Selon l'architecture actuelle du projet, créer un test ciblé ou extraire un
contrôleur testable sans afficher une fenêtre réelle.

Scénarios minimaux :

  1. ouverture d'une enquête valide ;
  2. passage immédiat à l'état de chargement ;
  3. réception d'un graphe valide ;
  4. stockage du graphe dans la fenêtre ;
  5. transmission empruntée au workspace ;
  6. remplacement d'un graphe existant ;
  7. libération de l'ancien graphe ;
  8. erreur de chargement ;
  9. état d'erreur du workspace ;
  10. annulation lors de la fermeture ;
  11. aucun callback visible après fermeture ;
  12. ouverture d'une seconde enquête pendant la première ;
  13. résultat de la première ignoré ;
  14. résultat de la seconde accepté ;
  15. graphe obsolète libéré ;
  16. fermeture de l'enquête active ;
  17. état vide restauré ;
  18. démarrage avec un chemin invalide ;
  19. tous les anciens tests restent valides.

Test d'un résultat obsolète

Le test doit contrôler deux chargements :

génération 1 démarrée
génération 2 démarrée
génération 2 terminée
génération 1 terminée tardivement

Vérifier que :

  • le graphe de génération 2 reste actif ;
  • le graphe de génération 1 est libéré ;
  • le workspace n'est pas remis dans un ancien état ;
  • aucun double free ne survient.

Utiliser des hooks déterministes plutôt que des délais arbitraires.

Makefile

Ajouter les nouvelles sources de production aux listes appropriées.

Ajouter les nouvelles cibles de test selon l'organisation actuelle du
Makefile.

Les tests doivent conserver :

-std=c17 -Wall -Wextra -Wpedantic -Werror

Les tests GTK doivent pouvoir être lancés dans l'environnement de test actuel
du projet. Ne pas introduire de dépendance à X11.

Hors périmètre

Ce ticket ne couvre pas :

  • le dessin du graphe ;
  • Cairo ;
  • le canvas interactif ;
  • le placement automatique des nœuds ;
  • les coordonnées des nœuds ;
  • le zoom ;
  • le déplacement à la souris ;
  • la sélection graphique ;
  • la création graphique de relations ;
  • l'édition des entités ;
  • l'édition des relations ;
  • la barre de progression détaillée ;
  • les preuves liées aux relations ;
  • la persistance de la disposition graphique.

Le ticket suivant créera la première vue GTK/Cairo du graphe chargé.

Critères d'acceptation

  • L'ouverture d'une enquête déclenche un chargement asynchrone.
  • Le thread GTK reste réactif.
  • Le workspace affiche un état de chargement.
  • Le graphe chargé est possédé par le contexte de l'enquête active.
  • Le workspace emprunte le graphe.
  • Le nombre d'entités et de relations est affiché après succès.
  • Les erreurs sont affichées proprement.
  • Une nouvelle ouverture annule l'ancienne tâche.
  • Les résultats obsolètes sont ignorés et libérés.
  • La fermeture annule la tâche active.
  • Aucun callback ne modifie une fenêtre détruite.
  • L'ancien graphe est libéré lors d'un remplacement.
  • Aucun accès direct au chargeur synchrone n'est réalisé depuis GTK.
  • Aucun dessin de graphe n'est introduit dans ce ticket.
  • Les tests ciblés passent.
  • make clean && make && make test réussit.
  • git diff --check ne retourne aucune erreur.
# Intégrer le chargement asynchrone du graphe dans la fenêtre principale ## Contexte Le projet possède désormais : - `InvestigationGraphModel` ; - `InvestigationGraphLoader` ; - `InvestigationGraphLoadTask` ; - une fenêtre principale GTK ; - un mécanisme d'ouverture d'enquête existant ; - un workspace destiné à accueillir les futures vues de l'enquête. Le chargement du graphe est désormais capable de s'exécuter dans un worker avec sa propre connexion SQLite et de restituer le résultat sur le thread principal. Cette fonctionnalité doit maintenant être intégrée au cycle de vie réel de l'application. ## Objectif Lorsqu'une enquête est ouverte dans la fenêtre principale : 1. récupérer le chemin de sa base SQLite ; 2. démarrer `InvestigationGraphLoadTask` ; 3. afficher un état de chargement non bloquant ; 4. recevoir le `InvestigationGraphModel` sur le thread GTK ; 5. conserver ce graphe dans le contexte de la fenêtre ou de l'enquête active ; 6. remettre le graphe au workspace ; 7. gérer les erreurs sans faire planter l'application ; 8. annuler proprement la tâche lors d'une fermeture ou d'un changement d'enquête. Ce ticket ne dessine pas encore le graphe. ## Architecture attendue Le cycle de vie doit être explicite : ```text ouverture de l'enquête ↓ création InvestigationGraphLoadTask ↓ affichage état "chargement" ↓ worker SQLite ↓ callback sur thread GTK ├── succès → stockage du graphe + mise à jour workspace └── erreur → affichage erreur + état vide ``` La fenêtre ne doit jamais appeler directement `investigation_graph_loader_load()`. ## Propriété du graphe Le propriétaire du graphe chargé doit être clairement défini. Architecture recommandée : ```text MainWindow / contexte d'enquête active possède InvestigationGraphModel Workspace emprunte InvestigationGraphModel ``` Règles : - le callback reçoit la propriété du graphe ; - l'objet propriétaire remplace éventuellement l'ancien graphe ; - l'ancien graphe est libéré avant ou au moment du remplacement ; - le workspace ne libère jamais le graphe emprunté ; - la fermeture de l'enquête libère le graphe ; - la fermeture de la fenêtre libère le graphe ; - aucun pointeur vers un ancien graphe ne doit rester dans les widgets. ## État privé à ajouter Le contexte propriétaire doit conserver au minimum : ```c InvestigationGraphLoadTask *graph_load_task; InvestigationGraphModel *graph_model; guint64 graph_load_generation; ``` `graph_load_generation` permet d'ignorer un résultat devenu obsolète lorsqu'une autre enquête a été ouverte avant la fin du chargement précédent. Une autre stratégie équivalente est acceptée si elle garantit le même résultat. ## Générations de chargement Chaque nouvelle ouverture d'enquête doit : 1. incrémenter un compteur de génération ; 2. annuler la tâche précédente ; 3. créer une nouvelle tâche ; 4. transmettre la génération courante dans les données du callback. Dans le callback : ```text génération reçue != génération actuelle ↓ résultat obsolète ↓ libération immédiate du graphe reçu ↓ aucune modification de l'interface ``` Même si l'annulation est demandée, cette vérification reste obligatoire car une ancienne tâche peut être en phase de finalisation. ## État de chargement dans le workspace Ajouter une API explicite au workspace, adaptée à son architecture actuelle. Exemple possible : ```c void workspace_set_graph_loading( Workspace *workspace ); void workspace_set_graph( Workspace *workspace, const InvestigationGraphModel *graph_model ); void workspace_set_graph_error( Workspace *workspace, const char *message ); void workspace_clear_graph( Workspace *workspace ); ``` Les noms exacts peuvent être adaptés aux conventions actuelles. ### Chargement Pendant le chargement, afficher au minimum : ```text Chargement de l'enquête… ``` Un `GtkSpinner` peut être utilisé. ### Succès À ce stade, le workspace peut afficher un résumé temporaire : ```text Graphe chargé 3 entités 5 relations ``` Ce résumé sert uniquement à valider l'intégration avant le futur canvas. ### Erreur Afficher un message compréhensible : ```text Impossible de charger le graphe de l'enquête. ``` Le détail technique de `GError` peut être : - journalisé ; - affiché dans une zone secondaire ; - ou intégré dans le message si cela reste lisible. Ne pas afficher seulement un message SQLite brut sans contexte. ## Démarrage du chargement Créer une fonction interne dédiée, par exemple : ```c static void main_window_start_graph_loading( MainWindow *main_window, const char *database_path ); ``` Elle doit : - vérifier les arguments ; - annuler et libérer l'ancienne tâche ; - libérer ou détacher l'ancien graphe ; - mettre le workspace en état de chargement ; - créer la nouvelle tâche ; - lancer la tâche ; - gérer un échec immédiat de création ou de démarrage. Ne pas disperser cette logique dans plusieurs callbacks GTK. ## Callback de fin Créer une fonction interne dédiée : ```c static void main_window_on_graph_loaded( InvestigationGraphLoadTask *load_task, InvestigationGraphModel *graph_model, const GError *error, gpointer user_data ); ``` Elle doit : - être exécutée sur le thread GTK ; - vérifier la génération ; - ne jamais conserver simultanément `graph_model` et `error` ; - transférer le graphe au propriétaire en cas de succès ; - mettre à jour le workspace ; - libérer un résultat obsolète ; - ne pas libérer l'erreur empruntée ; - ne pas détruire directement la tâche depuis un état incohérent. ## Données du callback Créer une petite structure possédée par la tâche : ```c typedef struct { MainWindow *main_window; guint64 generation; } MainWindowGraphLoadContext; ``` Ou une variante adaptée au type réel de la fenêtre. Son destructeur doit : - libérer uniquement les ressources qu'elle possède ; - ne pas détruire la fenêtre GTK ; - ne pas provoquer d'accès après destruction. Si la fenêtre n'est pas un objet GObject possédé par référence, prévoir un mécanisme sûr pour rendre le callback inactif après fermeture. ## Fermeture de la fenêtre Lors de la destruction de la fenêtre : 1. incrémenter ou invalider la génération ; 2. annuler la tâche active ; 3. libérer la tâche ; 4. détacher le graphe du workspace ; 5. libérer le graphe possédé ; 6. ne permettre aucun callback utilisateur ultérieur. Le cycle de vie garanti par `InvestigationGraphLoadTask` doit être utilisé, pas contourné. ## Changement d'enquête Si une seconde enquête est ouverte pendant le chargement de la première : - la première tâche est annulée ; - son résultat ne doit jamais remplacer la seconde enquête ; - le workspace affiche l'état de la seconde enquête ; - le graphe de la première est libéré s'il arrive tardivement ; - seule la génération courante peut modifier l'interface. ## Fermeture d'une enquête active Lorsqu'aucune enquête n'est active : - annuler le chargement courant ; - libérer le graphe courant ; - remettre le workspace dans son état initial ; - ne conserver aucun chemin SQLite obsolète. ## Erreurs immédiates Les cas suivants doivent être gérés : - chemin SQLite absent ; - allocation de `InvestigationGraphLoadTask` impossible ; - démarrage de la tâche refusé ; - tâche déjà active à cause d'une incohérence interne. Dans ces cas : - remettre le workspace en état d'erreur ; - journaliser le détail ; - libérer toutes les ressources déjà créées ; - ne pas laisser `graph_load_task` pointer vers un objet inutilisable. ## Journalisation Journaliser au minimum : ### Début ```text Chargement asynchrone du graphe : <chemin> ``` ### Succès ```text Graphe chargé : N entités, M relations ``` ### Annulation ```text Chargement du graphe annulé ``` ### Erreur ```text Échec du chargement du graphe : <message> ``` Ne jamais journaliser de données sensibles issues des entités. ## Tests Créer ou compléter les tests des composants concernés. ### Tests du workspace Tester : 1. état initial ; 2. état de chargement ; 3. affichage du spinner ; 4. état de succès ; 5. affichage du nombre d'entités ; 6. affichage du nombre de relations ; 7. état d'erreur ; 8. nettoyage du message précédent ; 9. retour à l'état vide ; 10. le workspace ne libère pas le graphe emprunté. ### Tests de l'intégration fenêtre Selon l'architecture actuelle du projet, créer un test ciblé ou extraire un contrôleur testable sans afficher une fenêtre réelle. Scénarios minimaux : 1. ouverture d'une enquête valide ; 2. passage immédiat à l'état de chargement ; 3. réception d'un graphe valide ; 4. stockage du graphe dans la fenêtre ; 5. transmission empruntée au workspace ; 6. remplacement d'un graphe existant ; 7. libération de l'ancien graphe ; 8. erreur de chargement ; 9. état d'erreur du workspace ; 10. annulation lors de la fermeture ; 11. aucun callback visible après fermeture ; 12. ouverture d'une seconde enquête pendant la première ; 13. résultat de la première ignoré ; 14. résultat de la seconde accepté ; 15. graphe obsolète libéré ; 16. fermeture de l'enquête active ; 17. état vide restauré ; 18. démarrage avec un chemin invalide ; 19. tous les anciens tests restent valides. ## Test d'un résultat obsolète Le test doit contrôler deux chargements : ```text génération 1 démarrée génération 2 démarrée génération 2 terminée génération 1 terminée tardivement ``` Vérifier que : - le graphe de génération 2 reste actif ; - le graphe de génération 1 est libéré ; - le workspace n'est pas remis dans un ancien état ; - aucun double `free` ne survient. Utiliser des hooks déterministes plutôt que des délais arbitraires. ## Makefile Ajouter les nouvelles sources de production aux listes appropriées. Ajouter les nouvelles cibles de test selon l'organisation actuelle du Makefile. Les tests doivent conserver : ```text -std=c17 -Wall -Wextra -Wpedantic -Werror ``` Les tests GTK doivent pouvoir être lancés dans l'environnement de test actuel du projet. Ne pas introduire de dépendance à X11. ## Hors périmètre Ce ticket ne couvre pas : - le dessin du graphe ; - Cairo ; - le canvas interactif ; - le placement automatique des nœuds ; - les coordonnées des nœuds ; - le zoom ; - le déplacement à la souris ; - la sélection graphique ; - la création graphique de relations ; - l'édition des entités ; - l'édition des relations ; - la barre de progression détaillée ; - les preuves liées aux relations ; - la persistance de la disposition graphique. Le ticket suivant créera la première vue GTK/Cairo du graphe chargé. ## Critères d'acceptation - [x] L'ouverture d'une enquête déclenche un chargement asynchrone. - [x] Le thread GTK reste réactif. - [x] Le workspace affiche un état de chargement. - [x] Le graphe chargé est possédé par le contexte de l'enquête active. - [x] Le workspace emprunte le graphe. - [x] Le nombre d'entités et de relations est affiché après succès. - [x] Les erreurs sont affichées proprement. - [x] Une nouvelle ouverture annule l'ancienne tâche. - [x] Les résultats obsolètes sont ignorés et libérés. - [x] La fermeture annule la tâche active. - [x] Aucun callback ne modifie une fenêtre détruite. - [x] L'ancien graphe est libéré lors d'un remplacement. - [x] Aucun accès direct au chargeur synchrone n'est réalisé depuis GTK. - [x] Aucun dessin de graphe n'est introduit dans ce ticket. - [x] Les tests ciblés passent. - [x] `make clean && make && make test` réussit. - [x] `git diff --check` ne retourne aucune erreur.
fy59 closed this issue 2026-07-21 12:24:48 +02:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: fy59/labfy-investigation#61
No description provided.