Factoriser le chargement et l’installation d’une enquête #34

Closed
opened 2026-07-18 08:57:12 +02:00 by fy59 · 0 comments
Owner

Ticket #032 — Factoriser le chargement et l’installation d’une enquête

Contexte

Après le ticket #031.1, l’application sait :

  • créer une enquête ;
  • ouvrir une enquête existante ;
  • remplacer la session active ;
  • fermer proprement l’application.

Cependant, les flux « Nouvelle enquête » et « Ouvrir une enquête » dupliquent encore une partie importante de la logique :

investigation_session_open()
→ récupération du projet
→ récupération du chemin racine
→ investigation_tree_builder_build()
→ application_install_session()
→ nettoyage en cas d’échec

Cette duplication augmentera avec les futures fonctions :

  • enquêtes récentes ;
  • ouverture depuis la ligne de commande ;
  • restauration de session ;
  • ouverture depuis un rapport ou une archive.

Objectif

Créer dans src/core/application.c une fonction interne unique qui ouvre et installe une enquête à partir de son dossier racine.

Contrat attendu :

static gboolean application_open_and_install_investigation(
    Application *application,
    const char *root_path,
    GError **error
);

Cette fonction doit garantir :

succès :
    Application devient propriétaire de la nouvelle session
    Application devient propriétaire du nouvel arbre
    ancienne session libérée
    ancien arbre libéré
    fenêtre mise à jour

échec :
    ancienne session conservée
    ancien arbre conservé
    nouvelle session libérée
    nouvel arbre libéré
    erreur transmise à l’appelant

Travail à réaliser

1. Ajouter un domaine d’erreur privé

Dans src/core/application.c, ajouter un domaine d’erreur uniquement utilisé par le contrôleur.

Exemple :

typedef enum
{
    APPLICATION_OPEN_ERROR_INVALID_ARGUMENT,
    APPLICATION_OPEN_ERROR_INVALID_PROJECT,
    APPLICATION_OPEN_ERROR_TREE_BUILD,
    APPLICATION_OPEN_ERROR_INSTALL
} ApplicationOpenError;

Ajouter :

#define APPLICATION_OPEN_ERROR \
    application_open_error_quark()

Puis :

static GQuark application_open_error_quark(void)
{
    return g_quark_from_static_string(
        "labfy-investigation-application-open-error"
    );
}

Le domaine reste privé à application.c.


2. Créer la fonction factorisée

Ajouter :

static gboolean application_open_and_install_investigation(
    Application *application,
    const char *root_path,
    GError **error
);

La fonction doit :

  1. valider application ;
  2. valider application->main_window ;
  3. valider root_path ;
  4. ouvrir une nouvelle InvestigationSession ;
  5. récupérer son InvestigationProject ;
  6. récupérer le chemin racine canonique ;
  7. construire un nouvel InvestigationTreeModel ;
  8. appeler application_install_session() ;
  9. transférer la propriété uniquement en cas de succès.

Règle de propriété

Avant application_install_session() :

la fonction possède new_session
la fonction possède new_tree_model

Après succès :

Application possède new_session
Application possède new_tree_model

Après échec :

la fonction doit libérer les objets qu’elle possède encore

Validation de GError

La fonction doit respecter la convention GLib :

g_return_val_if_fail(
    error == NULL || *error == NULL,
    FALSE
);

L’utilisation de g_return_val_if_fail() est acceptable ici pour vérifier le contrat du développeur.

Les erreurs utilisateur doivent être produites avec :

g_set_error()
g_set_error_literal()
g_propagate_prefixed_error()

3. Propager l’erreur de InvestigationSession

Si :

investigation_session_open()

échoue, la fonction doit conserver l’erreur métier d’origine et lui ajouter du contexte.

Exemple conceptuel :

Impossible d’ouvrir l’enquête : la base SQLite est absente

Ne pas remplacer l’erreur précise par un simple :

Erreur inconnue

lorsqu’un GError est disponible.


4. Refactoriser l’ouverture d’une enquête existante

application_on_folder_selected() ne doit plus contenir directement :

investigation_session_open()
investigation_tree_builder_build()
application_install_session()

Elle doit seulement :

  1. accepter l’annulation ;
  2. appeler application_open_and_install_investigation() ;
  3. journaliser l’erreur ;
  4. libérer le GError.

Structure attendue :

static void application_on_folder_selected(
    const char *folder_path,
    gpointer user_data
)
{
    Application *application = user_data;
    GError *error = NULL;

    if (application == NULL ||
        folder_path == NULL)
    {
        return;
    }

    if (!application_open_and_install_investigation(
            application,
            folder_path,
            &error
        ))
    {
        g_warning(
            "Impossible d'ouvrir l'enquête '%s' : %s",
            folder_path,
            error != NULL
                ? error->message
                : "erreur inconnue"
        );

        g_clear_error(&error);
    }
}

L’annulation ne doit pas produire de warning.


5. Refactoriser la création d’une enquête

application_on_create_investigation() doit conserver uniquement :

  1. la création physique du projet ;
  2. l’appel à la fonction factorisée ;
  3. le message spécifique indiquant que le projet existe sur le disque si son ouverture échoue.

Le flux devient :

investigation_project_create()
→ application_open_and_install_investigation()

La fonction ne doit plus reconstruire elle-même :

session
projet
chemin canonique
arbre
installation

En cas d’échec après création, le dossier créé reste volontairement sur le disque.

Le message doit être explicite :

L’enquête a été créée dans « ... », mais son ouverture a échoué : ...

6. Ne pas modifier application_install_session()

Cette fonction conserve sa responsabilité actuelle :

  • valider les objets prêts à installer ;
  • libérer l’ancien état ;
  • transférer la propriété ;
  • mettre à jour MainWindow.

Le nouveau helper prépare les objets.

application_install_session() réalise le remplacement final.


Pseudo-code de la fonction factorisée

static gboolean application_open_and_install_investigation(
    Application *application,
    const char *root_path,
    GError **error
)
{
    InvestigationSession *new_session = NULL;
    InvestigationTreeModel *new_tree_model = NULL;
    const InvestigationProject *project = NULL;
    const char *canonical_root_path = NULL;
    GError *session_error = NULL;

    valider les arguments;

    new_session = investigation_session_open(
        root_path,
        &session_error
    );

    si échec :
        propager session_error avec contexte;
        return FALSE;

    project = investigation_session_get_project(
        new_session
    );

    valider project;

    canonical_root_path =
        investigation_project_get_root_path(project);

    valider canonical_root_path;

    new_tree_model =
        investigation_tree_builder_build(
            canonical_root_path
        );

    si échec :
        produire GError;
        fermer new_session;
        return FALSE;

    si application_install_session() échoue :
        produire GError;
        libérer new_tree_model;
        fermer new_session;
        return FALSE;

    return TRUE;
}

Tests manuels

Création valide

  1. lancer l’application ;
  2. créer une enquête ;
  3. vérifier le titre ;
  4. vérifier la barre d’état ;
  5. vérifier l’arborescence.

Ouverture valide

  1. fermer et relancer ;
  2. ouvrir l’enquête créée ;
  3. vérifier le même résultat.

Remplacement valide

  1. ouvrir une enquête A ;
  2. ouvrir une enquête B ;
  3. vérifier que B remplace A.

Échec d’ouverture

  1. ouvrir une enquête valide A ;
  2. tenter d’ouvrir un dossier invalide ;
  3. vérifier que A reste active ;
  4. vérifier que l’erreur est journalisée.

Échec après création

Provoquer si possible un échec d’ouverture après création.

Vérifier :

  • le dossier créé reste présent ;
  • l’ancienne session reste active ;
  • aucun double free ;
  • aucun crash.

Annulation

Annuler le sélecteur de dossier.

Vérifier :

  • aucun warning ;
  • aucun changement d’état ;
  • aucun crash.

Critères d’acceptation

  • Une seule fonction ouvre une session et construit son arbre.
  • La création utilise cette fonction.
  • L’ouverture utilise cette fonction.
  • application_install_session() n’est pas dupliquée.
  • L’ancienne session reste active en cas d’échec.
  • L’ancien arbre reste actif en cas d’échec.
  • Les nouveaux objets sont libérés en cas d’échec.
  • Les erreurs de InvestigationSession sont propagées.
  • L’annulation ne produit pas d’erreur.
  • Aucun code SQLite direct n’est ajouté.
  • Aucun comportement GTK visible n’est cassé.
  • make réussit.
  • make test réussit.
  • git diff --check ne retourne aucune erreur.

Audit attendu

La logique d’ouverture ne doit apparaître qu’une seule fois :

rg -n \
    'investigation_session_open|investigation_tree_builder_build' \
    src/core/application.c

Résultat attendu :

une occurrence de investigation_session_open
une occurrence de investigation_tree_builder_build

La création et l’ouverture doivent appeler le helper :

rg -n \
    'application_open_and_install_investigation' \
    src/core/application.c

Vérifier l’absence de SQLite direct :

rg -n \
    'sqlite3_|#include <sqlite3.h>' \
    src/core/application.c

Résultat attendu :

aucune sortie

Vérifier le format :

git diff --check

Fichiers concernés

src/core/application.c

Aucune modification publique n’est normalement nécessaire dans :

include/core/application.h

Commit attendu

make clean
make
make test
git diff --check
git status --short
git add src/core/application.c
git diff --cached --stat
git diff --cached
git commit -m "refactor(core): centralize investigation loading"
git push

Résultat attendu

Après ce ticket, tous les futurs points d’entrée utiliseront le même flux :

Création
Ouverture manuelle
Enquêtes récentes
Ligne de commande
Restauration de session
        ↓
application_open_and_install_investigation()

Le ticket #033 pourra ensuite afficher graphiquement les GError déjà correctement produits par ce flux.

# Ticket #032 — Factoriser le chargement et l’installation d’une enquête ## Contexte Après le ticket #031.1, l’application sait : - créer une enquête ; - ouvrir une enquête existante ; - remplacer la session active ; - fermer proprement l’application. Cependant, les flux « Nouvelle enquête » et « Ouvrir une enquête » dupliquent encore une partie importante de la logique : ```text investigation_session_open() → récupération du projet → récupération du chemin racine → investigation_tree_builder_build() → application_install_session() → nettoyage en cas d’échec ``` Cette duplication augmentera avec les futures fonctions : - enquêtes récentes ; - ouverture depuis la ligne de commande ; - restauration de session ; - ouverture depuis un rapport ou une archive. ## Objectif Créer dans `src/core/application.c` une fonction interne unique qui ouvre et installe une enquête à partir de son dossier racine. Contrat attendu : ```c static gboolean application_open_and_install_investigation( Application *application, const char *root_path, GError **error ); ``` Cette fonction doit garantir : ```text succès : Application devient propriétaire de la nouvelle session Application devient propriétaire du nouvel arbre ancienne session libérée ancien arbre libéré fenêtre mise à jour échec : ancienne session conservée ancien arbre conservé nouvelle session libérée nouvel arbre libéré erreur transmise à l’appelant ``` --- # Travail à réaliser ## 1. Ajouter un domaine d’erreur privé Dans `src/core/application.c`, ajouter un domaine d’erreur uniquement utilisé par le contrôleur. Exemple : ```c typedef enum { APPLICATION_OPEN_ERROR_INVALID_ARGUMENT, APPLICATION_OPEN_ERROR_INVALID_PROJECT, APPLICATION_OPEN_ERROR_TREE_BUILD, APPLICATION_OPEN_ERROR_INSTALL } ApplicationOpenError; ``` Ajouter : ```c #define APPLICATION_OPEN_ERROR \ application_open_error_quark() ``` Puis : ```c static GQuark application_open_error_quark(void) { return g_quark_from_static_string( "labfy-investigation-application-open-error" ); } ``` Le domaine reste privé à `application.c`. --- ## 2. Créer la fonction factorisée Ajouter : ```c static gboolean application_open_and_install_investigation( Application *application, const char *root_path, GError **error ); ``` La fonction doit : 1. valider `application` ; 2. valider `application->main_window` ; 3. valider `root_path` ; 4. ouvrir une nouvelle `InvestigationSession` ; 5. récupérer son `InvestigationProject` ; 6. récupérer le chemin racine canonique ; 7. construire un nouvel `InvestigationTreeModel` ; 8. appeler `application_install_session()` ; 9. transférer la propriété uniquement en cas de succès. ### Règle de propriété Avant `application_install_session()` : ```text la fonction possède new_session la fonction possède new_tree_model ``` Après succès : ```text Application possède new_session Application possède new_tree_model ``` Après échec : ```text la fonction doit libérer les objets qu’elle possède encore ``` ### Validation de `GError` La fonction doit respecter la convention GLib : ```c g_return_val_if_fail( error == NULL || *error == NULL, FALSE ); ``` L’utilisation de `g_return_val_if_fail()` est acceptable ici pour vérifier le contrat du développeur. Les erreurs utilisateur doivent être produites avec : ```c g_set_error() g_set_error_literal() g_propagate_prefixed_error() ``` --- ## 3. Propager l’erreur de `InvestigationSession` Si : ```c investigation_session_open() ``` échoue, la fonction doit conserver l’erreur métier d’origine et lui ajouter du contexte. Exemple conceptuel : ```text Impossible d’ouvrir l’enquête : la base SQLite est absente ``` Ne pas remplacer l’erreur précise par un simple : ```text Erreur inconnue ``` lorsqu’un `GError` est disponible. --- ## 4. Refactoriser l’ouverture d’une enquête existante `application_on_folder_selected()` ne doit plus contenir directement : ```c investigation_session_open() investigation_tree_builder_build() application_install_session() ``` Elle doit seulement : 1. accepter l’annulation ; 2. appeler `application_open_and_install_investigation()` ; 3. journaliser l’erreur ; 4. libérer le `GError`. Structure attendue : ```c static void application_on_folder_selected( const char *folder_path, gpointer user_data ) { Application *application = user_data; GError *error = NULL; if (application == NULL || folder_path == NULL) { return; } if (!application_open_and_install_investigation( application, folder_path, &error )) { g_warning( "Impossible d'ouvrir l'enquête '%s' : %s", folder_path, error != NULL ? error->message : "erreur inconnue" ); g_clear_error(&error); } } ``` L’annulation ne doit pas produire de warning. --- ## 5. Refactoriser la création d’une enquête `application_on_create_investigation()` doit conserver uniquement : 1. la création physique du projet ; 2. l’appel à la fonction factorisée ; 3. le message spécifique indiquant que le projet existe sur le disque si son ouverture échoue. Le flux devient : ```text investigation_project_create() → application_open_and_install_investigation() ``` La fonction ne doit plus reconstruire elle-même : ```text session projet chemin canonique arbre installation ``` En cas d’échec après création, le dossier créé reste volontairement sur le disque. Le message doit être explicite : ```text L’enquête a été créée dans « ... », mais son ouverture a échoué : ... ``` --- ## 6. Ne pas modifier `application_install_session()` Cette fonction conserve sa responsabilité actuelle : - valider les objets prêts à installer ; - libérer l’ancien état ; - transférer la propriété ; - mettre à jour `MainWindow`. Le nouveau helper prépare les objets. `application_install_session()` réalise le remplacement final. --- # Pseudo-code de la fonction factorisée ```c static gboolean application_open_and_install_investigation( Application *application, const char *root_path, GError **error ) { InvestigationSession *new_session = NULL; InvestigationTreeModel *new_tree_model = NULL; const InvestigationProject *project = NULL; const char *canonical_root_path = NULL; GError *session_error = NULL; valider les arguments; new_session = investigation_session_open( root_path, &session_error ); si échec : propager session_error avec contexte; return FALSE; project = investigation_session_get_project( new_session ); valider project; canonical_root_path = investigation_project_get_root_path(project); valider canonical_root_path; new_tree_model = investigation_tree_builder_build( canonical_root_path ); si échec : produire GError; fermer new_session; return FALSE; si application_install_session() échoue : produire GError; libérer new_tree_model; fermer new_session; return FALSE; return TRUE; } ``` --- # Tests manuels ## Création valide 1. lancer l’application ; 2. créer une enquête ; 3. vérifier le titre ; 4. vérifier la barre d’état ; 5. vérifier l’arborescence. ## Ouverture valide 1. fermer et relancer ; 2. ouvrir l’enquête créée ; 3. vérifier le même résultat. ## Remplacement valide 1. ouvrir une enquête A ; 2. ouvrir une enquête B ; 3. vérifier que B remplace A. ## Échec d’ouverture 1. ouvrir une enquête valide A ; 2. tenter d’ouvrir un dossier invalide ; 3. vérifier que A reste active ; 4. vérifier que l’erreur est journalisée. ## Échec après création Provoquer si possible un échec d’ouverture après création. Vérifier : - le dossier créé reste présent ; - l’ancienne session reste active ; - aucun double `free` ; - aucun crash. ## Annulation Annuler le sélecteur de dossier. Vérifier : - aucun warning ; - aucun changement d’état ; - aucun crash. --- # Critères d’acceptation - [x] Une seule fonction ouvre une session et construit son arbre. - [x] La création utilise cette fonction. - [x] L’ouverture utilise cette fonction. - [x] `application_install_session()` n’est pas dupliquée. - [x] L’ancienne session reste active en cas d’échec. - [x] L’ancien arbre reste actif en cas d’échec. - [x] Les nouveaux objets sont libérés en cas d’échec. - [x] Les erreurs de `InvestigationSession` sont propagées. - [x] L’annulation ne produit pas d’erreur. - [x] Aucun code SQLite direct n’est ajouté. - [x] Aucun comportement GTK visible n’est cassé. - [x] `make` réussit. - [x] `make test` réussit. - [x] `git diff --check` ne retourne aucune erreur. --- # Audit attendu La logique d’ouverture ne doit apparaître qu’une seule fois : ```bash rg -n \ 'investigation_session_open|investigation_tree_builder_build' \ src/core/application.c ``` Résultat attendu : ```text une occurrence de investigation_session_open une occurrence de investigation_tree_builder_build ``` La création et l’ouverture doivent appeler le helper : ```bash rg -n \ 'application_open_and_install_investigation' \ src/core/application.c ``` Vérifier l’absence de SQLite direct : ```bash rg -n \ 'sqlite3_|#include <sqlite3.h>' \ src/core/application.c ``` Résultat attendu : ```text aucune sortie ``` Vérifier le format : ```bash git diff --check ``` --- # Fichiers concernés ```text src/core/application.c ``` Aucune modification publique n’est normalement nécessaire dans : ```text include/core/application.h ``` --- # Commit attendu ```bash make clean make make test git diff --check git status --short ``` ```bash git add src/core/application.c ``` ```bash git diff --cached --stat git diff --cached ``` ```bash git commit -m "refactor(core): centralize investigation loading" git push ``` --- # Résultat attendu Après ce ticket, tous les futurs points d’entrée utiliseront le même flux : ```text Création Ouverture manuelle Enquêtes récentes Ligne de commande Restauration de session ↓ application_open_and_install_investigation() ``` Le ticket #033 pourra ensuite afficher graphiquement les `GError` déjà correctement produits par ce flux.
fy59 closed this issue 2026-07-18 08:57:40 +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#34
No description provided.