diff --git a/labfy-investigation/docs/tickets/closed/TICKET-009.md b/labfy-investigation/docs/tickets/closed/TICKET-009.md new file mode 100644 index 0000000..e187542 --- /dev/null +++ b/labfy-investigation/docs/tickets/closed/TICKET-009.md @@ -0,0 +1,268 @@ +# Ticket #009 + +## Titre + +Ajouter les relations parent/enfants à `InvestigationNode`. + +--- + +## Objectif + +Faire évoluer `InvestigationNode` afin qu'un nœud puisse appartenir à une arborescence. + +Chaque nœud pourra : + +- connaître son parent ; +- posséder plusieurs enfants ; +- exposer ses enfants en lecture seule via une API publique. + +--- + +## Responsabilités + +Le module `InvestigationNode` doit : + +- mémoriser un pointeur vers son parent ; +- posséder un tableau dynamique d'enfants ; +- ajouter un enfant ; +- retourner un enfant par son index ; +- retourner le nombre d'enfants ; +- retourner son parent ; +- détruire récursivement les enfants qu'il possède. + +--- + +## Hors périmètre + +Ce ticket ne doit pas : + +- parcourir le système de fichiers ; +- construire automatiquement une arborescence depuis un dossier ; +- afficher quoi que ce soit avec GTK ; +- communiquer avec SQLite ; +- gérer le tri des enfants ; +- gérer la suppression individuelle d'un enfant ; +- gérer le déplacement d'un nœud entre deux parents. + +--- + +## Architecture + +```text +InvestigationTreeModel + │ + ▼ +InvestigationNode + │ + ├── InvestigationNode + ├── InvestigationNode + └── InvestigationNode +``` + +Le module appartient à la couche `core`. + +Il peut dépendre de GLib, mais jamais de GTK. + +--- + +## Gestion de la propriété + +Un nœud devient propriétaire de chaque enfant ajouté avec : + +```c +investigation_node_add_child(parent, child); +``` + +Après un ajout réussi : + +- `parent` possède `child` ; +- le code appelant ne doit plus libérer `child` directement ; +- `child` conserve une référence non propriétaire vers `parent`. + +Lors de la destruction du parent, tous ses enfants sont détruits récursivement. + +--- + +## Structure interne attendue + +```c +struct InvestigationNode +{ + char *name; + InvestigationNodeType type; + InvestigationNode *parent; + GPtrArray *children; +}; +``` + +La structure reste privée dans `investigation_node.c`. + +--- + +## Interface publique attendue + +Les fonctions existantes sont conservées : + +```c +InvestigationNode *investigation_node_new( + const char *name, + InvestigationNodeType type +); + +void investigation_node_free( + InvestigationNode *node +); + +const char *investigation_node_get_name( + const InvestigationNode *node +); + +InvestigationNodeType investigation_node_get_type( + const InvestigationNode *node +); +``` + +Les fonctions suivantes sont ajoutées : + +```c +bool investigation_node_add_child( + InvestigationNode *parent, + InvestigationNode *child +); + +const InvestigationNode *investigation_node_get_child( + const InvestigationNode *node, + size_t index +); + +size_t investigation_node_get_children_count( + const InvestigationNode *node +); + +const InvestigationNode *investigation_node_get_parent( + const InvestigationNode *node +); +``` + +--- + +## Comportement attendu + +Exemple : + +```c +InvestigationNode *root = NULL; +InvestigationNode *child = NULL; + +root = investigation_node_new( + "Template", + INVESTIGATION_NODE_DIRECTORY +); + +child = investigation_node_new( + "00_BaseDeDonnees", + INVESTIGATION_NODE_DIRECTORY +); + +if (!investigation_node_add_child(root, child)) +{ + investigation_node_free(child); + investigation_node_free(root); + return; +} +``` + +Après l'ajout : + +```text +Template +└── 00_BaseDeDonnees +``` + +Le nœud `root` devient propriétaire de `child`. + +Le nettoyage correct est uniquement : + +```c +investigation_node_free(root); +``` + +--- + +## Règles d'ajout + +`investigation_node_add_child()` doit refuser : + +- un parent `NULL` ; +- un enfant `NULL` ; +- un parent qui représente un fichier ; +- l'ajout d'un nœud comme enfant de lui-même ; +- un enfant possédant déjà un parent. + +En cas d'échec, la propriété de l'enfant reste au code appelant. + +--- + +## Dépendances + +- C17 ; +- GLib. + +Aucune dépendance GTK ou SQLite. + +--- + +## Contraintes techniques + +- structure opaque ; +- aucun état global ; +- utilisation de `GPtrArray` ; +- tableau créé avec une fonction de destruction ; +- documentation Doxygen ; +- compilation sans warning ; +- respect des conventions de nommage ; +- aucune modification directe du tableau en dehors du module. + +--- + +## Critères d'acceptation + +- [ ] Le projet compile sans warning. +- [ ] Un dossier peut posséder plusieurs enfants. +- [ ] Un fichier ne peut pas recevoir d'enfant. +- [ ] Un enfant connaît son parent. +- [ ] Le nombre d'enfants est correct. +- [ ] Un enfant peut être récupéré par son index. +- [ ] Un index invalide retourne `NULL`. +- [ ] Un enfant ne peut pas avoir deux parents. +- [ ] Un nœud ne peut pas être son propre enfant. +- [ ] La destruction d'un parent détruit récursivement ses enfants. +- [ ] Aucun code GTK. +- [ ] Aucun code SQLite. + +--- + +## Tests + +- créer un parent dossier ; +- ajouter un enfant dossier ; +- ajouter un enfant fichier ; +- vérifier le nombre d'enfants ; +- récupérer chaque enfant ; +- vérifier le parent d'un enfant ; +- tester un index invalide ; +- refuser l'ajout à un fichier ; +- refuser un parent `NULL` ; +- refuser un enfant `NULL` ; +- refuser l'auto-référence ; +- refuser un enfant possédant déjà un parent ; +- détruire un arbre complet ; +- appeler les getters avec `NULL`. + +--- + +## Commit attendu + +```text +feat(core): add investigation node hierarchy +``` diff --git a/labfy-investigation/docs/tickets/open/TICKET-009.md b/labfy-investigation/docs/tickets/open/TICKET-009.md deleted file mode 100644 index e69de29..0000000 diff --git a/labfy-investigation/include/core/investigation_node.h b/labfy-investigation/include/core/investigation_node.h index fc81fbb..794fe34 100644 --- a/labfy-investigation/include/core/investigation_node.h +++ b/labfy-investigation/include/core/investigation_node.h @@ -6,6 +6,10 @@ #ifndef LABFY_INVESTIGATION_INVESTIGATION_NODE_H #define LABFY_INVESTIGATION_INVESTIGATION_NODE_H + +#include +#include + /** * @brief Représentation opaque d'un nœud d'enquête. * @@ -77,4 +81,40 @@ InvestigationNodeType investigation_node_get_type( const InvestigationNode *node ); +/** + * @brief Ajoute un enfant à un dossier. + * + * En cas de succès, le parent devient propriétaire de l'enfant. + * + * @return true si l'ajout a réussi. + */ +bool investigation_node_add_child( + InvestigationNode *parent, + InvestigationNode *child +); + +/** + * @brief Retourne un enfant. + * + * @return L'enfant ou NULL. + */ +const InvestigationNode *investigation_node_get_child( + const InvestigationNode *node, + size_t index +); + +/** + * @brief Retourne le nombre d'enfants. + */ +size_t investigation_node_get_children_count( + const InvestigationNode *node +); + +/** + * @brief Retourne le parent d'un nœud. + */ +const InvestigationNode *investigation_node_get_parent( + const InvestigationNode *node +); + #endif diff --git a/labfy-investigation/labfy-investigation b/labfy-investigation/labfy-investigation index 5bd1848..521ce43 100755 Binary files a/labfy-investigation/labfy-investigation and b/labfy-investigation/labfy-investigation differ diff --git a/labfy-investigation/src/core/investigation_node.c b/labfy-investigation/src/core/investigation_node.c index 5e2806f..79c4412 100644 --- a/labfy-investigation/src/core/investigation_node.c +++ b/labfy-investigation/src/core/investigation_node.c @@ -10,11 +10,16 @@ /** * @struct InvestigationNode * @brief Représentation interne d'un nœud. + * + * Le nœud possède son nom et ses enfants. + * Le pointeur parent est une simple référence non propriétaire. */ struct InvestigationNode { char *name; InvestigationNodeType type; + InvestigationNode *parent; + GPtrArray *children; }; InvestigationNode *investigation_node_new( @@ -29,10 +34,7 @@ InvestigationNode *investigation_node_new( return NULL; } - node = g_new0( - InvestigationNode, - 1 - ); + node = g_new0(InvestigationNode, 1); if (node == NULL) { @@ -47,6 +49,16 @@ InvestigationNode *investigation_node_new( return NULL; } + node->children = g_ptr_array_new_with_free_func( + (GDestroyNotify) investigation_node_free + ); + + if (node->children == NULL) + { + investigation_node_free(node); + return NULL; + } + node->type = type; return node; @@ -61,8 +73,16 @@ void investigation_node_free( return; } - g_free(node->name); + /* + * Le tableau possède les enfants. + * TRUE demande à GLib de libérer aussi le contenu du tableau. + */ + if (node->children != NULL) + { + g_ptr_array_free(node->children, TRUE); + } + g_free(node->name); g_free(node); } @@ -89,3 +109,83 @@ InvestigationNodeType investigation_node_get_type( return node->type; } + +bool investigation_node_add_child( + InvestigationNode *parent, + InvestigationNode *child +) +{ + if (parent == NULL || child == NULL) + { + return false; + } + + if (parent->type != INVESTIGATION_NODE_DIRECTORY) + { + return false; + } + + if (parent == child) + { + return false; + } + + if (child->parent != NULL) + { + return false; + } + + child->parent = parent; + + g_ptr_array_add( + parent->children, + child + ); + + return true; +} + +const InvestigationNode *investigation_node_get_child( + const InvestigationNode *node, + size_t index +) +{ + if (node == NULL || node->children == NULL) + { + return NULL; + } + + if (index >= node->children->len) + { + return NULL; + } + + return g_ptr_array_index( + node->children, + index + ); +} + +size_t investigation_node_get_children_count( + const InvestigationNode *node +) +{ + if (node == NULL || node->children == NULL) + { + return 0; + } + + return node->children->len; +} + +const InvestigationNode *investigation_node_get_parent( + const InvestigationNode *node +) +{ + if (node == NULL) + { + return NULL; + } + + return node->parent; +} diff --git a/labfy-investigation/tests/test_investigation_node b/labfy-investigation/tests/test_investigation_node index 440ea25..fc36224 100755 Binary files a/labfy-investigation/tests/test_investigation_node and b/labfy-investigation/tests/test_investigation_node differ diff --git a/labfy-investigation/tests/test_investigation_node.c b/labfy-investigation/tests/test_investigation_node.c index f99a828..07a3557 100644 --- a/labfy-investigation/tests/test_investigation_node.c +++ b/labfy-investigation/tests/test_investigation_node.c @@ -27,6 +27,8 @@ static void test_directory_node(void) investigation_node_get_type(node) == INVESTIGATION_NODE_DIRECTORY ); + assert(investigation_node_get_parent(node) == NULL); + assert(investigation_node_get_children_count(node) == 0); investigation_node_free(node); } @@ -53,6 +55,140 @@ static void test_file_node(void) investigation_node_free(node); } +static void test_add_children(void) +{ + InvestigationNode *root = NULL; + InvestigationNode *database_directory = NULL; + InvestigationNode *database_file = NULL; + const InvestigationNode *returned_child = NULL; + + root = investigation_node_new( + "Template", + INVESTIGATION_NODE_DIRECTORY + ); + + database_directory = investigation_node_new( + "00_BaseDeDonnees", + INVESTIGATION_NODE_DIRECTORY + ); + + database_file = investigation_node_new( + "Enquete.sqlite", + INVESTIGATION_NODE_FILE + ); + + assert(root != NULL); + assert(database_directory != NULL); + assert(database_file != NULL); + + assert( + investigation_node_add_child( + root, + database_directory + ) + ); + + assert( + investigation_node_add_child( + database_directory, + database_file + ) + ); + + assert(investigation_node_get_children_count(root) == 1); + assert( + investigation_node_get_children_count( + database_directory + ) == 1 + ); + + returned_child = investigation_node_get_child(root, 0); + + assert(returned_child == database_directory); + assert( + investigation_node_get_parent(database_directory) == + root + ); + assert( + investigation_node_get_parent(database_file) == + database_directory + ); + + /* + * root possède database_directory, + * qui possède database_file. + * La destruction est donc récursive. + */ + investigation_node_free(root); +} + +static void test_invalid_additions(void) +{ + InvestigationNode *directory = NULL; + InvestigationNode *file = NULL; + InvestigationNode *child = NULL; + InvestigationNode *second_parent = NULL; + + directory = investigation_node_new( + "Directory", + INVESTIGATION_NODE_DIRECTORY + ); + + file = investigation_node_new( + "File.txt", + INVESTIGATION_NODE_FILE + ); + + child = investigation_node_new( + "Child", + INVESTIGATION_NODE_DIRECTORY + ); + + second_parent = investigation_node_new( + "SecondParent", + INVESTIGATION_NODE_DIRECTORY + ); + + assert(directory != NULL); + assert(file != NULL); + assert(child != NULL); + assert(second_parent != NULL); + + assert(!investigation_node_add_child(NULL, child)); + assert(!investigation_node_add_child(directory, NULL)); + assert(!investigation_node_add_child(file, child)); + assert(!investigation_node_add_child(directory, directory)); + + assert(investigation_node_add_child(directory, child)); + + assert(!investigation_node_add_child(second_parent, child)); + + /* + * directory possède désormais child. + */ + investigation_node_free(directory); + investigation_node_free(file); + investigation_node_free(second_parent); +} + +static void test_invalid_index(void) +{ + InvestigationNode *node = NULL; + + node = investigation_node_new( + "Template", + INVESTIGATION_NODE_DIRECTORY + ); + + assert(node != NULL); + + assert(investigation_node_get_child(node, 0) == NULL); + assert(investigation_node_get_child(node, 42) == NULL); + assert(investigation_node_get_child(NULL, 0) == NULL); + + investigation_node_free(node); +} + static void test_invalid_names(void) { assert( @@ -70,8 +206,12 @@ static void test_invalid_names(void) ); } -static void test_null_free(void) +static void test_null_behaviour(void) { + assert(investigation_node_get_name(NULL) == NULL); + assert(investigation_node_get_children_count(NULL) == 0); + assert(investigation_node_get_parent(NULL) == NULL); + investigation_node_free(NULL); } @@ -79,10 +219,15 @@ int main(void) { test_directory_node(); test_file_node(); + test_add_children(); + test_invalid_additions(); + test_invalid_index(); test_invalid_names(); - test_null_free(); + test_null_behaviour(); - printf("InvestigationNode : tous les tests sont valides.\n"); + printf( + "InvestigationNode : tous les tests de hiérarchie sont valides.\n" + ); return 0; }