feat(core): build investigation tree from filesystem
This commit is contained in:
parent
2bd29e6208
commit
a0e6743ac8
9 changed files with 917 additions and 4 deletions
19
Makefile
19
Makefile
|
|
@ -19,9 +19,9 @@ TEST_CFLAGS = -std=c17 \
|
||||||
-Wextra \
|
-Wextra \
|
||||||
-Werror \
|
-Werror \
|
||||||
-Iinclude \
|
-Iinclude \
|
||||||
$(shell $(PKG_CONFIG) --cflags glib-2.0)
|
$(shell $(PKG_CONFIG) --cflags glib-2.0 gio-2.0)
|
||||||
|
|
||||||
TEST_LDFLAGS = $(shell $(PKG_CONFIG) --libs glib-2.0)
|
TEST_LDFLAGS = $(shell $(PKG_CONFIG) --libs glib-2.0 gio-2.0)
|
||||||
|
|
||||||
SRC := $(shell find src -name "*.c")
|
SRC := $(shell find src -name "*.c")
|
||||||
|
|
||||||
|
|
@ -31,6 +31,7 @@ TARGET = labfy-investigation
|
||||||
|
|
||||||
TEST_NODE = tests/test_investigation_node
|
TEST_NODE = tests/test_investigation_node
|
||||||
TEST_TREE_MODEL = tests/test_investigation_tree_model
|
TEST_TREE_MODEL = tests/test_investigation_tree_model
|
||||||
|
TEST_TREE_BUILDER = tests/test_investigation_tree_builder
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
|
|
@ -48,10 +49,19 @@ $(TEST_TREE_MODEL): \
|
||||||
src/core/investigation_tree_model.c
|
src/core/investigation_tree_model.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||||
|
|
||||||
test: $(TEST_NODE) $(TEST_TREE_MODEL)
|
$(TEST_TREE_BUILDER): \
|
||||||
|
tests/test_investigation_tree_builder.c \
|
||||||
|
src/core/investigation_node.c \
|
||||||
|
src/core/investigation_tree_model.c \
|
||||||
|
src/core/investigation_tree_builder.c
|
||||||
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) \
|
||||||
|
$(shell $(PKG_CONFIG) --libs gio-2.0)
|
||||||
|
|
||||||
|
test: $(TEST_NODE) $(TEST_TREE_MODEL) $(TEST_TREE_BUILDER)
|
||||||
@echo "Exécution des tests..."
|
@echo "Exécution des tests..."
|
||||||
@./$(TEST_NODE)
|
@./$(TEST_NODE)
|
||||||
@./$(TEST_TREE_MODEL)
|
@./$(TEST_TREE_MODEL)
|
||||||
|
@./$(TEST_TREE_BUILDER)
|
||||||
@echo "Tous les tests sont valides."
|
@echo "Tous les tests sont valides."
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
|
|
@ -63,6 +73,7 @@ run: $(TARGET)
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJ) $(TARGET) \
|
rm -f $(OBJ) $(TARGET) \
|
||||||
$(TEST_NODE) \
|
$(TEST_NODE) \
|
||||||
$(TEST_TREE_MODEL)
|
$(TEST_TREE_MODEL) \
|
||||||
|
$(TEST_TREE_BUILDER)
|
||||||
|
|
||||||
.PHONY: clean run test
|
.PHONY: clean run test
|
||||||
|
|
|
||||||
243
docs/tickets/closed/TICKET-010.md
Normal file
243
docs/tickets/closed/TICKET-010.md
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
# Ticket #010
|
||||||
|
|
||||||
|
## Titre
|
||||||
|
|
||||||
|
Construire l'arborescence d'une enquête depuis le système de fichiers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Créer le module `InvestigationTreeBuilder`.
|
||||||
|
|
||||||
|
Ce module doit parcourir récursivement le dossier racine d'une enquête et
|
||||||
|
construire un `InvestigationTreeModel` représentant son contenu.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Responsabilités
|
||||||
|
|
||||||
|
Le module `InvestigationTreeBuilder` doit :
|
||||||
|
|
||||||
|
- recevoir le chemin racine d'une enquête ;
|
||||||
|
- vérifier que ce chemin désigne un dossier existant ;
|
||||||
|
- créer le nœud racine ;
|
||||||
|
- parcourir récursivement les dossiers ;
|
||||||
|
- créer un `InvestigationNode` pour chaque dossier ;
|
||||||
|
- créer un `InvestigationNode` pour chaque fichier ;
|
||||||
|
- assembler les relations parent/enfants ;
|
||||||
|
- retourner un `InvestigationTreeModel` complet ;
|
||||||
|
- nettoyer toutes les ressources en cas d'erreur.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hors périmètre
|
||||||
|
|
||||||
|
Ce ticket ne doit pas :
|
||||||
|
|
||||||
|
- afficher l'arborescence avec GTK ;
|
||||||
|
- modifier le système de fichiers ;
|
||||||
|
- créer ou supprimer des fichiers ;
|
||||||
|
- trier les éléments ;
|
||||||
|
- filtrer les fichiers cachés ;
|
||||||
|
- surveiller les changements du disque ;
|
||||||
|
- suivre les liens symboliques ;
|
||||||
|
- communiquer avec SQLite.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```text
|
||||||
|
Investigation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
InvestigationTreeBuilder
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
InvestigationTreeModel
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
InvestigationNode
|
||||||
|
```
|
||||||
|
|
||||||
|
Le module appartient à la couche `core`.
|
||||||
|
|
||||||
|
Il peut utiliser GLib et GIO, mais ne doit jamais dépendre de GTK.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fichiers concernés
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/core/investigation_tree_builder.h
|
||||||
|
src/core/investigation_tree_builder.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Les tests seront placés dans :
|
||||||
|
|
||||||
|
```text
|
||||||
|
tests/test_investigation_tree_builder.c
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interface publique attendue
|
||||||
|
|
||||||
|
```c
|
||||||
|
InvestigationTreeModel *investigation_tree_builder_build(
|
||||||
|
const char *root_path
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Principe de propriété
|
||||||
|
|
||||||
|
En cas de succès, la fonction retourne un nouveau
|
||||||
|
`InvestigationTreeModel`.
|
||||||
|
|
||||||
|
Le code appelant devient propriétaire du modèle retourné et doit le libérer
|
||||||
|
avec :
|
||||||
|
|
||||||
|
```c
|
||||||
|
investigation_tree_model_free(tree_model);
|
||||||
|
```
|
||||||
|
|
||||||
|
En cas d'échec, la fonction retourne `NULL` et doit avoir libéré toutes les
|
||||||
|
ressources créées pendant la construction.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comportement attendu
|
||||||
|
|
||||||
|
À partir de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Enquete_Test/
|
||||||
|
├── 00_BaseDeDonnees/
|
||||||
|
│ └── Enquete.sqlite
|
||||||
|
├── 01_Preuves_Originales/
|
||||||
|
│ ├── Captures_Ecran/
|
||||||
|
│ └── Emails/
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
le module doit construire en mémoire :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Enquete_Test
|
||||||
|
├── 00_BaseDeDonnees
|
||||||
|
│ └── Enquete.sqlite
|
||||||
|
├── 01_Preuves_Originales
|
||||||
|
│ ├── Captures_Ecran
|
||||||
|
│ └── Emails
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gestion des liens symboliques
|
||||||
|
|
||||||
|
Les liens symboliques ne sont pas suivis.
|
||||||
|
|
||||||
|
Cette règle évite :
|
||||||
|
|
||||||
|
- les boucles récursives ;
|
||||||
|
- la sortie involontaire du dossier d'enquête ;
|
||||||
|
- l'analyse de fichiers extérieurs à l'enquête.
|
||||||
|
|
||||||
|
Leur prise en charge éventuelle fera l'objet d'un ticket distinct.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dépendances
|
||||||
|
|
||||||
|
- C17 ;
|
||||||
|
- GLib ;
|
||||||
|
- GIO.
|
||||||
|
|
||||||
|
Aucune dépendance GTK ou SQLite.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contraintes techniques
|
||||||
|
|
||||||
|
- aucune variable globale ;
|
||||||
|
- aucune modification du système de fichiers ;
|
||||||
|
- parcours récursif ;
|
||||||
|
- utilisation de `GFile` et `GFileEnumerator` ;
|
||||||
|
- libération correcte des `GObject` avec `g_object_unref()` ;
|
||||||
|
- respect de la règle « le propriétaire détruit » ;
|
||||||
|
- documentation Doxygen ;
|
||||||
|
- compilation sans warning ;
|
||||||
|
- aucune fuite mémoire.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cas à gérer
|
||||||
|
|
||||||
|
- chemin valide ;
|
||||||
|
- chemin relatif ;
|
||||||
|
- chemin `NULL` ;
|
||||||
|
- chemin vide ;
|
||||||
|
- chemin inexistant ;
|
||||||
|
- chemin désignant un fichier ;
|
||||||
|
- dossier vide ;
|
||||||
|
- plusieurs niveaux de sous-dossiers ;
|
||||||
|
- fichiers et dossiers mélangés ;
|
||||||
|
- erreur rencontrée pendant le parcours.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critères d'acceptation
|
||||||
|
|
||||||
|
- [ ] Le projet compile sans warning.
|
||||||
|
- [ ] Un dossier valide produit un modèle.
|
||||||
|
- [ ] Le nom du dossier racine est correct.
|
||||||
|
- [ ] Les dossiers produisent des nœuds de type `DIRECTORY`.
|
||||||
|
- [ ] Les fichiers produisent des nœuds de type `FILE`.
|
||||||
|
- [ ] Les relations parent/enfants sont correctes.
|
||||||
|
- [ ] Plusieurs niveaux de profondeur sont pris en charge.
|
||||||
|
- [ ] Un dossier vide est pris en charge.
|
||||||
|
- [ ] Un chemin invalide retourne `NULL`.
|
||||||
|
- [ ] Les liens symboliques ne sont pas suivis.
|
||||||
|
- [ ] Toutes les ressources sont libérées en cas d'erreur.
|
||||||
|
- [ ] Aucun code GTK.
|
||||||
|
- [ ] Aucun code SQLite.
|
||||||
|
- [ ] `make test` exécute le nouveau test.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Le test doit créer une arborescence temporaire isolée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
TestCase/
|
||||||
|
├── DirectoryA/
|
||||||
|
│ └── FileA.txt
|
||||||
|
├── DirectoryB/
|
||||||
|
└── RootFile.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Il doit vérifier :
|
||||||
|
|
||||||
|
- le nom du nœud racine ;
|
||||||
|
- le nombre d'enfants de la racine ;
|
||||||
|
- la présence des deux dossiers ;
|
||||||
|
- la présence du fichier racine ;
|
||||||
|
- la présence de `FileA.txt` dans `DirectoryA` ;
|
||||||
|
- le type de chaque nœud ;
|
||||||
|
- le parent de chaque enfant ;
|
||||||
|
- la gestion d'un dossier vide ;
|
||||||
|
- les chemins invalides ;
|
||||||
|
- la destruction complète du modèle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commit attendu
|
||||||
|
|
||||||
|
```text
|
||||||
|
feat(core): build investigation tree from filesystem
|
||||||
|
```
|
||||||
36
include/core/investigation_tree_builder.h
Normal file
36
include/core/investigation_tree_builder.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file investigation_tree_builder.h
|
||||||
|
* @brief Interface publique du constructeur d'arborescence d'une enquête.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_INVESTIGATION_TREE_BUILDER_H
|
||||||
|
#define LABFY_INVESTIGATION_INVESTIGATION_TREE_BUILDER_H
|
||||||
|
|
||||||
|
#include "core/investigation_tree_model.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construit le modèle d'arborescence d'une enquête depuis le disque.
|
||||||
|
*
|
||||||
|
* Le chemin fourni doit désigner un dossier existant.
|
||||||
|
*
|
||||||
|
* La fonction parcourt récursivement son contenu et crée :
|
||||||
|
*
|
||||||
|
* - un nœud racine pour le dossier sélectionné ;
|
||||||
|
* - un nœud de type DIRECTORY pour chaque sous-dossier ;
|
||||||
|
* - un nœud de type FILE pour chaque fichier.
|
||||||
|
*
|
||||||
|
* Les liens symboliques ne sont pas suivis.
|
||||||
|
*
|
||||||
|
* Le code appelant devient propriétaire du modèle retourné et doit le
|
||||||
|
* libérer avec investigation_tree_model_free().
|
||||||
|
*
|
||||||
|
* @param root_path Chemin du dossier racine à parcourir.
|
||||||
|
*
|
||||||
|
* @return Un nouveau modèle d'arborescence, ou NULL si le chemin est invalide
|
||||||
|
* ou si une erreur empêche la construction du modèle.
|
||||||
|
*/
|
||||||
|
InvestigationTreeModel *investigation_tree_builder_build(
|
||||||
|
const char *root_path
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
Binary file not shown.
285
src/core/investigation_tree_builder.c
Normal file
285
src/core/investigation_tree_builder.c
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file investigation_tree_builder.c
|
||||||
|
* @brief Construction d'une arborescence d'enquête depuis le système de fichiers.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "core/investigation_tree_builder.h"
|
||||||
|
|
||||||
|
#include "core/investigation_node.h"
|
||||||
|
#include "core/investigation_tree_model.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Attributs demandés à GIO pendant le parcours d'un dossier.
|
||||||
|
*/
|
||||||
|
#define INVESTIGATION_TREE_FILE_ATTRIBUTES \
|
||||||
|
G_FILE_ATTRIBUTE_STANDARD_NAME "," \
|
||||||
|
G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
|
||||||
|
G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construit récursivement les enfants d'un nœud dossier.
|
||||||
|
*
|
||||||
|
* @param directory Dossier GIO actuellement parcouru.
|
||||||
|
* @param parent_node Nœud représentant ce dossier dans notre modèle.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur GIO.
|
||||||
|
*
|
||||||
|
* @return true si le parcours s'est terminé correctement, sinon false.
|
||||||
|
*/
|
||||||
|
static bool investigation_tree_builder_build_children(
|
||||||
|
GFile *directory,
|
||||||
|
InvestigationNode *parent_node,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GFileEnumerator *enumerator = NULL;
|
||||||
|
GFileInfo *file_info = NULL;
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
if (directory == NULL || parent_node == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
enumerator = g_file_enumerate_children(
|
||||||
|
directory,
|
||||||
|
INVESTIGATION_TREE_FILE_ATTRIBUTES,
|
||||||
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||||
|
NULL,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (enumerator == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (success)
|
||||||
|
{
|
||||||
|
const char *child_name = NULL;
|
||||||
|
GFileType child_file_type;
|
||||||
|
GFile *child_file = NULL;
|
||||||
|
InvestigationNode *child_node = NULL;
|
||||||
|
InvestigationNodeType child_node_type;
|
||||||
|
|
||||||
|
file_info = g_file_enumerator_next_file(
|
||||||
|
enumerator,
|
||||||
|
NULL,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (file_info == NULL)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* NULL signifie soit que le dossier est entièrement parcouru,
|
||||||
|
* soit qu'une erreur a eu lieu.
|
||||||
|
*/
|
||||||
|
if (error != NULL && *error != NULL)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Les liens symboliques sont volontairement ignorés afin d'éviter
|
||||||
|
* les boucles et les sorties involontaires du dossier d'enquête.
|
||||||
|
*/
|
||||||
|
if (g_file_info_get_is_symlink(file_info))
|
||||||
|
{
|
||||||
|
g_object_unref(file_info);
|
||||||
|
file_info = NULL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
child_name = g_file_info_get_name(file_info);
|
||||||
|
|
||||||
|
if (child_name == NULL || child_name[0] == '\0')
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
g_object_unref(file_info);
|
||||||
|
file_info = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
child_file_type = g_file_info_get_file_type(file_info);
|
||||||
|
|
||||||
|
if (child_file_type == G_FILE_TYPE_DIRECTORY)
|
||||||
|
{
|
||||||
|
child_node_type = INVESTIGATION_NODE_DIRECTORY;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
child_node_type = INVESTIGATION_NODE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
child_node = investigation_node_new(
|
||||||
|
child_name,
|
||||||
|
child_node_type
|
||||||
|
);
|
||||||
|
|
||||||
|
if (child_node == NULL)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
g_object_unref(file_info);
|
||||||
|
file_info = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* En cas de succès, parent_node devient propriétaire de child_node.
|
||||||
|
*/
|
||||||
|
if (!investigation_node_add_child(parent_node, child_node))
|
||||||
|
{
|
||||||
|
investigation_node_free(child_node);
|
||||||
|
success = false;
|
||||||
|
g_object_unref(file_info);
|
||||||
|
file_info = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Seuls les dossiers sont parcourus récursivement.
|
||||||
|
*/
|
||||||
|
if (child_node_type == INVESTIGATION_NODE_DIRECTORY)
|
||||||
|
{
|
||||||
|
child_file = g_file_get_child(
|
||||||
|
directory,
|
||||||
|
child_name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (child_file == NULL)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
g_object_unref(file_info);
|
||||||
|
file_info = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
success = investigation_tree_builder_build_children(
|
||||||
|
child_file,
|
||||||
|
child_node,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_object_unref(child_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref(file_info);
|
||||||
|
file_info = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Si la boucle a été interrompue avant la libération de file_info,
|
||||||
|
* cette vérification garantit un nettoyage correct.
|
||||||
|
*/
|
||||||
|
if (file_info != NULL)
|
||||||
|
{
|
||||||
|
g_object_unref(file_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref(enumerator);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
InvestigationTreeModel *investigation_tree_builder_build(
|
||||||
|
const char *root_path
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GFile *root_file = NULL;
|
||||||
|
GFileType root_file_type;
|
||||||
|
char *root_name = NULL;
|
||||||
|
InvestigationNode *root_node = NULL;
|
||||||
|
InvestigationTreeModel *tree_model = NULL;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
if (root_path == NULL || root_path[0] == '\0')
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
root_file = g_file_new_for_path(root_path);
|
||||||
|
|
||||||
|
if (root_file == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
root_file_type = g_file_query_file_type(
|
||||||
|
root_file,
|
||||||
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (root_file_type != G_FILE_TYPE_DIRECTORY)
|
||||||
|
{
|
||||||
|
g_object_unref(root_file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
root_name = g_file_get_basename(root_file);
|
||||||
|
|
||||||
|
if (root_name == NULL || root_name[0] == '\0')
|
||||||
|
{
|
||||||
|
g_free(root_name);
|
||||||
|
g_object_unref(root_file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
root_node = investigation_node_new(
|
||||||
|
root_name,
|
||||||
|
INVESTIGATION_NODE_DIRECTORY
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(root_name);
|
||||||
|
|
||||||
|
if (root_node == NULL)
|
||||||
|
{
|
||||||
|
g_object_unref(root_file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!investigation_tree_builder_build_children(
|
||||||
|
root_file,
|
||||||
|
root_node,
|
||||||
|
&error
|
||||||
|
))
|
||||||
|
{
|
||||||
|
if (error != NULL)
|
||||||
|
{
|
||||||
|
g_warning(
|
||||||
|
"Impossible de construire l'arborescence : %s",
|
||||||
|
error->message
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(&error);
|
||||||
|
}
|
||||||
|
|
||||||
|
investigation_node_free(root_node);
|
||||||
|
g_object_unref(root_file);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref(root_file);
|
||||||
|
|
||||||
|
tree_model = investigation_tree_model_new(root_node);
|
||||||
|
|
||||||
|
if (tree_model == NULL)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Le transfert de propriété n'a pas eu lieu puisque la création
|
||||||
|
* du modèle a échoué.
|
||||||
|
*/
|
||||||
|
investigation_node_free(root_node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree_model;
|
||||||
|
}
|
||||||
Binary file not shown.
BIN
tests/test_investigation_tree_builder
Executable file
BIN
tests/test_investigation_tree_builder
Executable file
Binary file not shown.
338
tests/test_investigation_tree_builder.c
Normal file
338
tests/test_investigation_tree_builder.c
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file test_investigation_tree_builder.c
|
||||||
|
* @brief Tests d'intégration du module InvestigationTreeBuilder.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "core/investigation_node.h"
|
||||||
|
#include "core/investigation_tree_builder.h"
|
||||||
|
#include "core/investigation_tree_model.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib/gstdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Recherche un enfant par son nom.
|
||||||
|
*
|
||||||
|
* @param parent Nœud dans lequel effectuer la recherche.
|
||||||
|
* @param name Nom recherché.
|
||||||
|
*
|
||||||
|
* @return L'enfant correspondant, ou NULL s'il n'existe pas.
|
||||||
|
*/
|
||||||
|
static const InvestigationNode *test_find_child(
|
||||||
|
const InvestigationNode *parent,
|
||||||
|
const char *name
|
||||||
|
)
|
||||||
|
{
|
||||||
|
size_t child_count = 0;
|
||||||
|
|
||||||
|
if (parent == NULL || name == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
child_count = investigation_node_get_children_count(parent);
|
||||||
|
|
||||||
|
for (size_t index = 0; index < child_count; ++index)
|
||||||
|
{
|
||||||
|
const InvestigationNode *child = NULL;
|
||||||
|
const char *child_name = NULL;
|
||||||
|
|
||||||
|
child = investigation_node_get_child(parent, index);
|
||||||
|
|
||||||
|
if (child == NULL)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
child_name = investigation_node_get_name(child);
|
||||||
|
|
||||||
|
if (child_name != NULL && strcmp(child_name, name) == 0)
|
||||||
|
{
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crée un fichier texte utilisé par le test.
|
||||||
|
*/
|
||||||
|
static void test_create_file(const char *file_path)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
assert(file_path != NULL);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
g_file_set_contents(
|
||||||
|
file_path,
|
||||||
|
"test\n",
|
||||||
|
-1,
|
||||||
|
&error
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(error == NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie la construction récursive d'une arborescence.
|
||||||
|
*/
|
||||||
|
static void test_build_tree_from_filesystem(void)
|
||||||
|
{
|
||||||
|
char *root_path = NULL;
|
||||||
|
char *root_name = NULL;
|
||||||
|
|
||||||
|
char *directory_a_path = NULL;
|
||||||
|
char *directory_b_path = NULL;
|
||||||
|
char *file_a_path = NULL;
|
||||||
|
char *root_file_path = NULL;
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
InvestigationTreeModel *tree_model = NULL;
|
||||||
|
const InvestigationNode *root_node = NULL;
|
||||||
|
const InvestigationNode *directory_a_node = NULL;
|
||||||
|
const InvestigationNode *directory_b_node = NULL;
|
||||||
|
const InvestigationNode *file_a_node = NULL;
|
||||||
|
const InvestigationNode *root_file_node = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GLib crée un dossier temporaire unique, par exemple :
|
||||||
|
*
|
||||||
|
* /tmp/labfy-investigation-test-ABC123
|
||||||
|
*/
|
||||||
|
root_path = g_dir_make_tmp(
|
||||||
|
"labfy-investigation-test-XXXXXX",
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(root_path != NULL);
|
||||||
|
assert(error == NULL);
|
||||||
|
|
||||||
|
directory_a_path = g_build_filename(
|
||||||
|
root_path,
|
||||||
|
"DirectoryA",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
directory_b_path = g_build_filename(
|
||||||
|
root_path,
|
||||||
|
"DirectoryB",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
file_a_path = g_build_filename(
|
||||||
|
directory_a_path,
|
||||||
|
"FileA.txt",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
root_file_path = g_build_filename(
|
||||||
|
root_path,
|
||||||
|
"RootFile.md",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(g_mkdir(directory_a_path, 0700) == 0);
|
||||||
|
assert(g_mkdir(directory_b_path, 0700) == 0);
|
||||||
|
|
||||||
|
test_create_file(file_a_path);
|
||||||
|
test_create_file(root_file_path);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Arborescence créée :
|
||||||
|
*
|
||||||
|
* racine temporaire/
|
||||||
|
* ├── DirectoryA/
|
||||||
|
* │ └── FileA.txt
|
||||||
|
* ├── DirectoryB/
|
||||||
|
* └── RootFile.md
|
||||||
|
*/
|
||||||
|
tree_model = investigation_tree_builder_build(root_path);
|
||||||
|
|
||||||
|
assert(tree_model != NULL);
|
||||||
|
|
||||||
|
root_node = investigation_tree_model_get_root(tree_model);
|
||||||
|
|
||||||
|
assert(root_node != NULL);
|
||||||
|
assert(
|
||||||
|
investigation_node_get_type(root_node) ==
|
||||||
|
INVESTIGATION_NODE_DIRECTORY
|
||||||
|
);
|
||||||
|
|
||||||
|
root_name = g_path_get_basename(root_path);
|
||||||
|
|
||||||
|
assert(root_name != NULL);
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
investigation_node_get_name(root_node),
|
||||||
|
root_name
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(investigation_node_get_children_count(root_node) == 3);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* L'ordre retourné par le système de fichiers n'est pas garanti.
|
||||||
|
* On recherche donc chaque enfant par son nom.
|
||||||
|
*/
|
||||||
|
directory_a_node = test_find_child(
|
||||||
|
root_node,
|
||||||
|
"DirectoryA"
|
||||||
|
);
|
||||||
|
|
||||||
|
directory_b_node = test_find_child(
|
||||||
|
root_node,
|
||||||
|
"DirectoryB"
|
||||||
|
);
|
||||||
|
|
||||||
|
root_file_node = test_find_child(
|
||||||
|
root_node,
|
||||||
|
"RootFile.md"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(directory_a_node != NULL);
|
||||||
|
assert(directory_b_node != NULL);
|
||||||
|
assert(root_file_node != NULL);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
investigation_node_get_type(directory_a_node) ==
|
||||||
|
INVESTIGATION_NODE_DIRECTORY
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
investigation_node_get_type(directory_b_node) ==
|
||||||
|
INVESTIGATION_NODE_DIRECTORY
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
investigation_node_get_type(root_file_node) ==
|
||||||
|
INVESTIGATION_NODE_FILE
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
investigation_node_get_parent(directory_a_node) ==
|
||||||
|
root_node
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
investigation_node_get_parent(directory_b_node) ==
|
||||||
|
root_node
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
investigation_node_get_parent(root_file_node) ==
|
||||||
|
root_node
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DirectoryB est vide.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
investigation_node_get_children_count(directory_b_node) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DirectoryA contient FileA.txt.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
investigation_node_get_children_count(directory_a_node) == 1
|
||||||
|
);
|
||||||
|
|
||||||
|
file_a_node = test_find_child(
|
||||||
|
directory_a_node,
|
||||||
|
"FileA.txt"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(file_a_node != NULL);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
investigation_node_get_type(file_a_node) ==
|
||||||
|
INVESTIGATION_NODE_FILE
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
investigation_node_get_parent(file_a_node) ==
|
||||||
|
directory_a_node
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Le modèle possède la racine et toute son arborescence.
|
||||||
|
*/
|
||||||
|
investigation_tree_model_free(tree_model);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nettoyage du système de fichiers dans l'ordre inverse
|
||||||
|
* de la création.
|
||||||
|
*/
|
||||||
|
assert(g_remove(file_a_path) == 0);
|
||||||
|
assert(g_remove(root_file_path) == 0);
|
||||||
|
assert(g_rmdir(directory_a_path) == 0);
|
||||||
|
assert(g_rmdir(directory_b_path) == 0);
|
||||||
|
assert(g_rmdir(root_path) == 0);
|
||||||
|
|
||||||
|
g_free(root_file_path);
|
||||||
|
g_free(file_a_path);
|
||||||
|
g_free(directory_b_path);
|
||||||
|
g_free(directory_a_path);
|
||||||
|
g_free(root_name);
|
||||||
|
g_free(root_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie les chemins invalides.
|
||||||
|
*/
|
||||||
|
static void test_invalid_paths(void)
|
||||||
|
{
|
||||||
|
char *temporary_file_path = NULL;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
assert(investigation_tree_builder_build(NULL) == NULL);
|
||||||
|
assert(investigation_tree_builder_build("") == NULL);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
investigation_tree_builder_build(
|
||||||
|
"/chemin/qui/nexiste/pas"
|
||||||
|
) == NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
temporary_file_path = g_build_filename(
|
||||||
|
g_get_tmp_dir(),
|
||||||
|
"labfy-investigation-builder-file-test.txt",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
test_create_file(temporary_file_path);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Le chemin existe, mais il désigne un fichier et non un dossier.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
investigation_tree_builder_build(
|
||||||
|
temporary_file_path
|
||||||
|
) == NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(g_remove(temporary_file_path) == 0);
|
||||||
|
|
||||||
|
g_free(temporary_file_path);
|
||||||
|
g_clear_error(&error);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
test_build_tree_from_filesystem();
|
||||||
|
test_invalid_paths();
|
||||||
|
|
||||||
|
printf(
|
||||||
|
"InvestigationTreeBuilder : tous les tests sont valides.\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Binary file not shown.
Loading…
Reference in a new issue