feat(graph): add movable relation nodes
This commit is contained in:
parent
b1d0ec2a1e
commit
3d358a5e09
16 changed files with 1370 additions and 325 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Build
|
||||
labfy-investigation
|
||||
*.o
|
||||
*.d
|
||||
|
||||
# Executables de tests
|
||||
/tests/test_*
|
||||
|
|
|
|||
9
Makefile
9
Makefile
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
CC = gcc
|
||||
|
||||
PKG_CONFIG = pkg-config
|
||||
|
|
@ -11,6 +10,8 @@ CFLAGS = -std=c17 \
|
|||
-Werror \
|
||||
-g \
|
||||
-Iinclude \
|
||||
-MMD \
|
||||
-MP \
|
||||
$(shell $(PKG_CONFIG) --cflags gtk4 sqlite3)
|
||||
|
||||
LDFLAGS = $(shell $(PKG_CONFIG) --libs gtk4 sqlite3)
|
||||
|
|
@ -62,6 +63,8 @@ SRC := $(shell find src -name "*.c")
|
|||
|
||||
OBJ := $(SRC:.c=.o)
|
||||
|
||||
DEP := $(OBJ:.o=.d)
|
||||
|
||||
TARGET = labfy-investigation
|
||||
|
||||
TEST_NODE = tests/test_investigation_node
|
||||
|
|
@ -649,7 +652,7 @@ run: $(TARGET)
|
|||
./$(TARGET)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJ) $(TARGET) \
|
||||
rm -f $(OBJ) $(DEP) $(TARGET) \
|
||||
$(TEST_NODE) \
|
||||
$(TEST_TREE_MODEL) \
|
||||
$(TEST_TREE_BUILDER) \
|
||||
|
|
@ -695,4 +698,6 @@ clean:
|
|||
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \
|
||||
$(TEST_GRAPH_NODE_POSITION_DAO)
|
||||
|
||||
-include $(DEP)
|
||||
|
||||
.PHONY: clean run test
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -23,16 +23,6 @@ Le projet vise à fournir un environnement local, modulaire et traçable pour or
|
|||
|
||||
---
|
||||
|
||||
Labfy Investigation est un poste de travail libre d’investigation numérique et d’OSINT, développé en **C17** avec **GTK4**.
|
||||
|
||||
Le projet vise à fournir un environnement local, modulaire et traçable pour organiser une enquête, préserver les preuves originales, analyser des données, corréler des entités et produire des rapports exploitables.
|
||||
|
||||
> **État du projet : développement actif**
|
||||
>
|
||||
> Le logiciel n’est pas encore prêt pour un usage opérationnel en production. Les formats internes, l’interface et les mécanismes d’intégration peuvent encore évoluer.
|
||||
|
||||
---
|
||||
|
||||
## Objectifs
|
||||
|
||||
Labfy Investigation doit permettre de :
|
||||
|
|
|
|||
|
|
@ -57,3 +57,45 @@ CREATE TABLE IF NOT EXISTS graph_node_positions
|
|||
length(trim(updated_at)) > 0
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* Disposition générique du graphe.
|
||||
*
|
||||
* Contrairement à graph_node_positions, cette table accepte aussi les UUID
|
||||
* des relations. L'ancienne table reste présente pour assurer la compatibilité
|
||||
* avec les enquêtes créées avant l'introduction des nœuds de relation.
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS graph_layout_positions
|
||||
(
|
||||
node_id TEXT PRIMARY KEY,
|
||||
x REAL NOT NULL,
|
||||
y REAL NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
|
||||
CHECK (length(trim(node_id)) > 0),
|
||||
CHECK (length(updated_at) = 20)
|
||||
);
|
||||
|
||||
/* Migration idempotente des positions d'entités déjà enregistrées. */
|
||||
INSERT OR IGNORE INTO graph_layout_positions(node_id, x, y, updated_at)
|
||||
SELECT entity_id, x, y, updated_at
|
||||
FROM graph_node_positions;
|
||||
|
||||
/* Évite qu'une réinitialisation future ne réimporte des coordonnées obsolètes. */
|
||||
DELETE FROM graph_node_positions;
|
||||
|
||||
/* Une clé étrangère polymorphe n'existe pas dans SQLite : ces triggers
|
||||
* suppriment donc les positions devenues orphelines. */
|
||||
CREATE TRIGGER IF NOT EXISTS graph_layout_positions_delete_entity
|
||||
AFTER DELETE ON entites
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
DELETE FROM graph_layout_positions WHERE node_id = OLD.id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS graph_layout_positions_delete_relation
|
||||
AFTER DELETE ON relations
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
DELETE FROM graph_layout_positions WHERE node_id = OLD.id;
|
||||
END;
|
||||
|
|
|
|||
|
|
@ -2181,6 +2181,30 @@ des fichiers ou des notes.
|
|||
|
||||
---
|
||||
|
||||
## 11.5 Disposition générique du graphe
|
||||
|
||||
La disposition visuelle du graphe est un état de présentation et non une
|
||||
donnée métier. Elle est stockée dans `graph_layout_positions`, séparément des
|
||||
tables `entites` et `relations`.
|
||||
|
||||
Chaque ligne associe un UUID de nœud à des coordonnées logiques et à une date
|
||||
UTC de mise à jour. Un nœud peut représenter une entité ou une relation. SQLite
|
||||
ne proposant pas de clé étrangère polymorphe, l'intégrité de ce stockage est
|
||||
assurée par deux triggers qui suppriment la position correspondante lors de la
|
||||
suppression physique d'une entité ou d'une relation.
|
||||
|
||||
La table historique `graph_node_positions` ne référençait que `entites(id)`.
|
||||
À l'ouverture d'une enquête, ses lignes sont copiées de manière idempotente
|
||||
vers `graph_layout_positions`, puis retirées de la table historique afin
|
||||
qu'une réinitialisation volontaire de la disposition ne puisse pas restaurer
|
||||
des coordonnées obsolètes.
|
||||
|
||||
Cette stratégie préserve les positions existantes tout en permettant aux
|
||||
nœuds de relation de suivre exactement le même cycle de chargement,
|
||||
d'enregistrement et de réinitialisation que les nœuds d'entité.
|
||||
|
||||
---
|
||||
|
||||
# 12. Conclusion
|
||||
|
||||
La base de données de Labfy Investigation constitue bien davantage qu'un simple
|
||||
|
|
|
|||
|
|
@ -91,12 +91,12 @@ GPtrArray *graph_node_position_dao_list_all(
|
|||
);
|
||||
|
||||
/**
|
||||
* @brief Insère ou met à jour la position d'une entité.
|
||||
* @brief Insère ou met à jour la position d'un nœud.
|
||||
*
|
||||
* La date updated_at est générée en UTC par le DAO.
|
||||
*
|
||||
* @param position_dao DAO valide.
|
||||
* @param entity_identifier UUID de l'entité.
|
||||
* @param node_identifier UUID du nœud (entité ou relation).
|
||||
* @param x Coordonnée horizontale logique.
|
||||
* @param y Coordonnée verticale logique.
|
||||
* @param error Adresse recevant une éventuelle erreur.
|
||||
|
|
@ -105,26 +105,26 @@ GPtrArray *graph_node_position_dao_list_all(
|
|||
*/
|
||||
gboolean graph_node_position_dao_upsert(
|
||||
GraphNodePositionDao *position_dao,
|
||||
const char *entity_identifier,
|
||||
const char *node_identifier,
|
||||
double x,
|
||||
double y,
|
||||
GError **error
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Supprime la position persistée d'une entité.
|
||||
* @brief Supprime la position persistée d'un nœud.
|
||||
*
|
||||
* L'absence de position n'est pas une erreur.
|
||||
*
|
||||
* @param position_dao DAO valide.
|
||||
* @param entity_identifier UUID de l'entité.
|
||||
* @param node_identifier UUID du nœud (entité ou relation).
|
||||
* @param error Adresse recevant une éventuelle erreur.
|
||||
*
|
||||
* @return TRUE si la requête réussit, sinon FALSE.
|
||||
*/
|
||||
gboolean graph_node_position_dao_delete(
|
||||
GraphNodePositionDao *position_dao,
|
||||
const char *entity_identifier,
|
||||
const char *node_identifier,
|
||||
GError **error
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -42,11 +42,11 @@ GQuark graph_node_position_error_quark(void);
|
|||
*
|
||||
* Toutes les chaînes sont copiées.
|
||||
*
|
||||
* entity_identifier doit être un UUID valide.
|
||||
* node_identifier doit être un UUID valide.
|
||||
* x et y doivent être des nombres finis.
|
||||
* updated_at doit respecter le format UTC YYYY-MM-DDTHH:MM:SSZ.
|
||||
*
|
||||
* @param entity_identifier UUID de l'entité.
|
||||
* @param node_identifier UUID du nœud (entité ou relation).
|
||||
* @param x Coordonnée horizontale logique.
|
||||
* @param y Coordonnée verticale logique.
|
||||
* @param updated_at Date UTC de dernière modification.
|
||||
|
|
@ -55,7 +55,7 @@ GQuark graph_node_position_error_quark(void);
|
|||
* @return Nouvelle position, ou NULL lorsque les données sont invalides.
|
||||
*/
|
||||
GraphNodePosition *graph_node_position_new(
|
||||
const char *entity_identifier,
|
||||
const char *node_identifier,
|
||||
double x,
|
||||
double y,
|
||||
const char *updated_at,
|
||||
|
|
@ -74,7 +74,7 @@ void graph_node_position_free(
|
|||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne l'UUID de l'entité.
|
||||
* @brief Retourne l'UUID du nœud (entité ou relation).
|
||||
*
|
||||
* La chaîne retournée appartient au modèle.
|
||||
*
|
||||
|
|
@ -82,7 +82,7 @@ void graph_node_position_free(
|
|||
*
|
||||
* @return UUID emprunté, ou NULL.
|
||||
*/
|
||||
const char *graph_node_position_get_entity_identifier(
|
||||
const char *graph_node_position_get_node_identifier(
|
||||
const GraphNodePosition *position
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -47,16 +47,16 @@ typedef void (*InvestigationGraphViewSelectionCallback)(
|
|||
* @brief Callback appelé après le déplacement effectif d'un nœud.
|
||||
*
|
||||
* Les coordonnées sont exprimées dans l'espace logique du graphe.
|
||||
* entity_identifier est emprunté au modèle et reste valable uniquement
|
||||
* node_identifier est emprunté au modèle et reste valable uniquement
|
||||
* pendant l'appel.
|
||||
*
|
||||
* @param entity_identifier UUID de l'entité déplacée.
|
||||
* @param node_identifier UUID du nœud déplacé (entité ou relation).
|
||||
* @param x Coordonnée horizontale logique du coin supérieur gauche.
|
||||
* @param y Coordonnée verticale logique du coin supérieur gauche.
|
||||
* @param user_data Données empruntées fournies par l'appelant.
|
||||
*/
|
||||
typedef void (*InvestigationGraphViewNodeMovedCallback)(
|
||||
const char *entity_identifier,
|
||||
const char *node_identifier,
|
||||
double x,
|
||||
double y,
|
||||
gpointer user_data
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "widgets/evidence_category_model.h"
|
||||
#include "core/evidence_integrity_task.h"
|
||||
#include "core/evidence_integrity_verifier.h"
|
||||
#include "core/relation_service.h"
|
||||
#include "database/database.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
|
@ -3341,6 +3342,21 @@ static void application_on_create_relation_completed(
|
|||
Application *application =
|
||||
user_data;
|
||||
|
||||
const InvestigationProject *project =
|
||||
NULL;
|
||||
|
||||
Database *database =
|
||||
NULL;
|
||||
|
||||
RelationService *relation_service =
|
||||
NULL;
|
||||
|
||||
RelationRecord *relation_record =
|
||||
NULL;
|
||||
|
||||
GDateTime *current_date_time =
|
||||
NULL;
|
||||
|
||||
const char *source_identifier =
|
||||
NULL;
|
||||
|
||||
|
|
@ -3350,9 +3366,21 @@ static void application_on_create_relation_completed(
|
|||
const char *relation_type =
|
||||
NULL;
|
||||
|
||||
char *status_message =
|
||||
const char *database_path =
|
||||
NULL;
|
||||
|
||||
char *relation_identifier =
|
||||
NULL;
|
||||
|
||||
char *timestamp =
|
||||
NULL;
|
||||
|
||||
GError *error =
|
||||
NULL;
|
||||
|
||||
gboolean relation_created =
|
||||
FALSE;
|
||||
|
||||
if (application == NULL ||
|
||||
application->main_window == NULL)
|
||||
{
|
||||
|
|
@ -3373,6 +3401,17 @@ static void application_on_create_relation_completed(
|
|||
return;
|
||||
}
|
||||
|
||||
if (application->session == NULL)
|
||||
{
|
||||
application_present_error(
|
||||
application,
|
||||
"Ajout de relation impossible",
|
||||
"Aucune enquête n'est actuellement ouverte."
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
source_identifier =
|
||||
create_relation_dialog_result_get_source_identifier(
|
||||
result
|
||||
|
|
@ -3388,41 +3427,225 @@ static void application_on_create_relation_completed(
|
|||
result
|
||||
);
|
||||
|
||||
g_message(
|
||||
"Relation préparée : %s -> %s (%s), confiance %d %%.",
|
||||
source_identifier != NULL
|
||||
? source_identifier
|
||||
: "(source absente)",
|
||||
target_identifier != NULL
|
||||
? target_identifier
|
||||
: "(cible absente)",
|
||||
relation_type != NULL
|
||||
? relation_type
|
||||
: "(type absent)",
|
||||
create_relation_dialog_result_get_confidence(
|
||||
result
|
||||
)
|
||||
);
|
||||
|
||||
status_message =
|
||||
g_strdup_printf(
|
||||
"Relation préparée : %s. "
|
||||
"L'enregistrement SQLite sera branché à l'étape suivante.",
|
||||
relation_type != NULL &&
|
||||
relation_type[0] != '\0'
|
||||
? relation_type
|
||||
: "(type inconnu)"
|
||||
database =
|
||||
investigation_session_get_database(
|
||||
application->session
|
||||
);
|
||||
|
||||
project =
|
||||
investigation_session_get_project(
|
||||
application->session
|
||||
);
|
||||
|
||||
if (database == NULL ||
|
||||
project == NULL)
|
||||
{
|
||||
application_present_error(
|
||||
application,
|
||||
"Ajout de relation impossible",
|
||||
"La session d'enquête est invalide."
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
database_path =
|
||||
investigation_project_get_database_path(
|
||||
project
|
||||
);
|
||||
|
||||
if (database_path == NULL ||
|
||||
database_path[0] == '\0')
|
||||
{
|
||||
application_present_error(
|
||||
application,
|
||||
"Ajout de relation impossible",
|
||||
"Le chemin de la base SQLite est invalide."
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
relation_identifier =
|
||||
g_uuid_string_random();
|
||||
|
||||
current_date_time =
|
||||
g_date_time_new_now_utc();
|
||||
|
||||
if (current_date_time != NULL)
|
||||
{
|
||||
timestamp =
|
||||
g_date_time_format(
|
||||
current_date_time,
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
);
|
||||
}
|
||||
|
||||
if (relation_identifier == NULL ||
|
||||
timestamp == NULL)
|
||||
{
|
||||
application_present_error(
|
||||
application,
|
||||
"Ajout de relation impossible",
|
||||
"Impossible de préparer l'identifiant ou la date "
|
||||
"de la nouvelle relation."
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
relation_record =
|
||||
relation_record_new(
|
||||
relation_identifier,
|
||||
source_identifier,
|
||||
target_identifier,
|
||||
relation_type,
|
||||
create_relation_dialog_result_get_label(
|
||||
result
|
||||
),
|
||||
create_relation_dialog_result_get_justification(
|
||||
result
|
||||
),
|
||||
create_relation_dialog_result_get_confidence(
|
||||
result
|
||||
),
|
||||
timestamp,
|
||||
timestamp,
|
||||
RELATION_STATUS_ACTIVE,
|
||||
&error
|
||||
);
|
||||
|
||||
if (relation_record == NULL)
|
||||
{
|
||||
application_present_error(
|
||||
application,
|
||||
"Relation invalide",
|
||||
error != NULL
|
||||
? error->message
|
||||
: "Les informations de la relation sont invalides."
|
||||
);
|
||||
|
||||
g_clear_error(
|
||||
&error
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
relation_service =
|
||||
relation_service_new(
|
||||
database,
|
||||
&error
|
||||
);
|
||||
|
||||
if (relation_service == NULL)
|
||||
{
|
||||
application_present_error(
|
||||
application,
|
||||
"Ajout de relation impossible",
|
||||
error != NULL
|
||||
? error->message
|
||||
: "Impossible de préparer le service des relations."
|
||||
);
|
||||
|
||||
g_clear_error(
|
||||
&error
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* Aucune preuve n'est associée pendant ce premier parcours.
|
||||
* Le service accepte donc un tableau NULL.
|
||||
*/
|
||||
if (!relation_service_create(
|
||||
relation_service,
|
||||
relation_record,
|
||||
NULL,
|
||||
&error
|
||||
))
|
||||
{
|
||||
g_warning(
|
||||
"Impossible d'enregistrer la relation '%s' : %s",
|
||||
relation_identifier,
|
||||
error != NULL
|
||||
? error->message
|
||||
: "erreur inconnue"
|
||||
);
|
||||
|
||||
application_present_error(
|
||||
application,
|
||||
"Enregistrement de la relation impossible",
|
||||
error != NULL
|
||||
? error->message
|
||||
: "La relation n'a pas pu être enregistrée."
|
||||
);
|
||||
|
||||
g_clear_error(
|
||||
&error
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
relation_created =
|
||||
TRUE;
|
||||
|
||||
g_message(
|
||||
"Relation enregistrée : %s -> %s (%s), identifiant %s.",
|
||||
source_identifier,
|
||||
target_identifier,
|
||||
relation_type,
|
||||
relation_identifier
|
||||
);
|
||||
|
||||
main_window_set_status(
|
||||
application->main_window,
|
||||
status_message != NULL
|
||||
? status_message
|
||||
: "Relation préparée."
|
||||
"Relation enregistrée. Actualisation du graphe…"
|
||||
);
|
||||
|
||||
/*
|
||||
* Le graphe n'est rechargé qu'après le COMMIT réussi.
|
||||
* En cas d'échec SQLite, l'affichage courant reste intact.
|
||||
*/
|
||||
application_start_graph_loading(
|
||||
application,
|
||||
database_path
|
||||
);
|
||||
|
||||
cleanup:
|
||||
|
||||
if (!relation_created &&
|
||||
application->main_window != NULL)
|
||||
{
|
||||
main_window_set_status(
|
||||
application->main_window,
|
||||
"La relation n'a pas été enregistrée."
|
||||
);
|
||||
}
|
||||
|
||||
relation_service_free(
|
||||
relation_service
|
||||
);
|
||||
|
||||
relation_record_free(
|
||||
relation_record
|
||||
);
|
||||
|
||||
if (current_date_time != NULL)
|
||||
{
|
||||
g_date_time_unref(
|
||||
current_date_time
|
||||
);
|
||||
}
|
||||
|
||||
g_free(
|
||||
timestamp
|
||||
);
|
||||
|
||||
g_free(
|
||||
status_message
|
||||
relation_identifier
|
||||
);
|
||||
|
||||
create_relation_dialog_result_free(
|
||||
|
|
|
|||
|
|
@ -946,7 +946,7 @@ static gboolean investigation_graph_load_task_worker(
|
|||
|
||||
if (!investigation_graph_layout_set_position(
|
||||
graph_layout,
|
||||
graph_node_position_get_entity_identifier(
|
||||
graph_node_position_get_node_identifier(
|
||||
position
|
||||
),
|
||||
graph_node_position_get_x(
|
||||
|
|
|
|||
|
|
@ -27,20 +27,20 @@ struct GraphNodePositionDao
|
|||
*/
|
||||
static const char *const graph_node_position_dao_list_all_sql =
|
||||
"SELECT "
|
||||
" entity_id,"
|
||||
" node_id,"
|
||||
" x,"
|
||||
" y,"
|
||||
" updated_at "
|
||||
"FROM graph_node_positions "
|
||||
"ORDER BY entity_id ASC;";
|
||||
"FROM graph_layout_positions "
|
||||
"ORDER BY node_id ASC;";
|
||||
|
||||
/**
|
||||
* @brief Requête d'insertion ou de mise à jour d'une position.
|
||||
*/
|
||||
static const char *const graph_node_position_dao_upsert_sql =
|
||||
"INSERT INTO graph_node_positions"
|
||||
"INSERT INTO graph_layout_positions"
|
||||
"("
|
||||
" entity_id,"
|
||||
" node_id,"
|
||||
" x,"
|
||||
" y,"
|
||||
" updated_at"
|
||||
|
|
@ -52,7 +52,7 @@ static const char *const graph_node_position_dao_upsert_sql =
|
|||
" ?,"
|
||||
" ?"
|
||||
")"
|
||||
"ON CONFLICT(entity_id)"
|
||||
"ON CONFLICT(node_id)"
|
||||
"DO UPDATE SET "
|
||||
" x = excluded.x,"
|
||||
" y = excluded.y,"
|
||||
|
|
@ -62,14 +62,14 @@ static const char *const graph_node_position_dao_upsert_sql =
|
|||
* @brief Requête de suppression d'une position.
|
||||
*/
|
||||
static const char *const graph_node_position_dao_delete_sql =
|
||||
"DELETE FROM graph_node_positions "
|
||||
"WHERE entity_id = ?;";
|
||||
"DELETE FROM graph_layout_positions "
|
||||
"WHERE node_id = ?;";
|
||||
|
||||
/**
|
||||
* @brief Requête de suppression de toutes les positions.
|
||||
*/
|
||||
static const char *const graph_node_position_dao_delete_all_sql =
|
||||
"DELETE FROM graph_node_positions;";
|
||||
"DELETE FROM graph_layout_positions;";
|
||||
|
||||
/**
|
||||
* @brief Enregistre une erreur littérale.
|
||||
|
|
@ -202,7 +202,7 @@ graph_node_position_dao_read_current_record(
|
|||
GraphNodePosition *position =
|
||||
NULL;
|
||||
|
||||
char *entity_identifier =
|
||||
char *node_identifier =
|
||||
NULL;
|
||||
|
||||
char *updated_at =
|
||||
|
|
@ -233,7 +233,7 @@ graph_node_position_dao_read_current_record(
|
|||
if (!database_statement_column_text(
|
||||
statement,
|
||||
0,
|
||||
&entity_identifier
|
||||
&node_identifier
|
||||
) ||
|
||||
!database_statement_column_double(
|
||||
statement,
|
||||
|
|
@ -262,7 +262,7 @@ graph_node_position_dao_read_current_record(
|
|||
|
||||
position =
|
||||
graph_node_position_new(
|
||||
entity_identifier,
|
||||
node_identifier,
|
||||
x,
|
||||
y,
|
||||
updated_at,
|
||||
|
|
@ -302,7 +302,7 @@ cleanup:
|
|||
);
|
||||
|
||||
g_free(
|
||||
entity_identifier
|
||||
node_identifier
|
||||
);
|
||||
|
||||
return position;
|
||||
|
|
@ -576,7 +576,7 @@ error:
|
|||
|
||||
gboolean graph_node_position_dao_upsert(
|
||||
GraphNodePositionDao *position_dao,
|
||||
const char *entity_identifier,
|
||||
const char *node_identifier,
|
||||
double x,
|
||||
double y,
|
||||
GError **error
|
||||
|
|
@ -598,9 +598,9 @@ gboolean graph_node_position_dao_upsert(
|
|||
|
||||
if (position_dao == NULL ||
|
||||
position_dao->database == NULL ||
|
||||
entity_identifier == NULL ||
|
||||
node_identifier == NULL ||
|
||||
!g_uuid_string_is_valid(
|
||||
entity_identifier
|
||||
node_identifier
|
||||
) ||
|
||||
!isfinite(x) ||
|
||||
!isfinite(y))
|
||||
|
|
@ -649,7 +649,7 @@ gboolean graph_node_position_dao_upsert(
|
|||
if (!database_statement_bind_text(
|
||||
statement,
|
||||
1,
|
||||
entity_identifier
|
||||
node_identifier
|
||||
) ||
|
||||
!database_statement_bind_double(
|
||||
statement,
|
||||
|
|
@ -707,7 +707,7 @@ cleanup:
|
|||
|
||||
gboolean graph_node_position_dao_delete(
|
||||
GraphNodePositionDao *position_dao,
|
||||
const char *entity_identifier,
|
||||
const char *node_identifier,
|
||||
GError **error
|
||||
)
|
||||
{
|
||||
|
|
@ -724,15 +724,15 @@ gboolean graph_node_position_dao_delete(
|
|||
|
||||
if (position_dao == NULL ||
|
||||
position_dao->database == NULL ||
|
||||
entity_identifier == NULL ||
|
||||
node_identifier == NULL ||
|
||||
!g_uuid_string_is_valid(
|
||||
entity_identifier
|
||||
node_identifier
|
||||
))
|
||||
{
|
||||
graph_node_position_dao_set_error_literal(
|
||||
error,
|
||||
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
|
||||
"L'identifiant de l'entité est invalide."
|
||||
"L'identifiant du nœud est invalide."
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
|
|
@ -759,14 +759,14 @@ gboolean graph_node_position_dao_delete(
|
|||
if (!database_statement_bind_text(
|
||||
statement,
|
||||
1,
|
||||
entity_identifier
|
||||
node_identifier
|
||||
))
|
||||
{
|
||||
graph_node_position_dao_set_database_error(
|
||||
position_dao,
|
||||
error,
|
||||
GRAPH_NODE_POSITION_DAO_ERROR_BIND,
|
||||
"Impossible de lier l'identifiant de l'entité"
|
||||
"Impossible de lier l'identifiant du nœud"
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
*/
|
||||
struct GraphNodePosition
|
||||
{
|
||||
char *entity_identifier;
|
||||
char *node_identifier;
|
||||
|
||||
double x;
|
||||
double y;
|
||||
|
|
@ -154,7 +154,7 @@ GQuark graph_node_position_error_quark(void)
|
|||
}
|
||||
|
||||
GraphNodePosition *graph_node_position_new(
|
||||
const char *entity_identifier,
|
||||
const char *node_identifier,
|
||||
double x,
|
||||
double y,
|
||||
const char *updated_at,
|
||||
|
|
@ -164,7 +164,7 @@ GraphNodePosition *graph_node_position_new(
|
|||
GraphNodePosition *position =
|
||||
NULL;
|
||||
|
||||
char *entity_identifier_copy =
|
||||
char *node_identifier_copy =
|
||||
NULL;
|
||||
|
||||
char *updated_at_copy =
|
||||
|
|
@ -175,22 +175,22 @@ GraphNodePosition *graph_node_position_new(
|
|||
NULL
|
||||
);
|
||||
|
||||
entity_identifier_copy =
|
||||
node_identifier_copy =
|
||||
graph_node_position_duplicate_trimmed(
|
||||
entity_identifier
|
||||
node_identifier
|
||||
);
|
||||
|
||||
if (entity_identifier_copy == NULL ||
|
||||
entity_identifier_copy[0] == '\0' ||
|
||||
if (node_identifier_copy == NULL ||
|
||||
node_identifier_copy[0] == '\0' ||
|
||||
!g_uuid_string_is_valid(
|
||||
entity_identifier_copy
|
||||
node_identifier_copy
|
||||
))
|
||||
{
|
||||
g_set_error_literal(
|
||||
error,
|
||||
GRAPH_NODE_POSITION_ERROR,
|
||||
GRAPH_NODE_POSITION_ERROR_INVALID_IDENTIFIER,
|
||||
"L'identifiant de l'entité n'est pas un UUID valide."
|
||||
"L'identifiant du nœud n'est pas un UUID valide."
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
|
|
@ -246,8 +246,8 @@ GraphNodePosition *graph_node_position_new(
|
|||
goto cleanup;
|
||||
}
|
||||
|
||||
position->entity_identifier =
|
||||
entity_identifier_copy;
|
||||
position->node_identifier =
|
||||
node_identifier_copy;
|
||||
|
||||
position->x =
|
||||
x;
|
||||
|
|
@ -258,7 +258,7 @@ GraphNodePosition *graph_node_position_new(
|
|||
position->updated_at =
|
||||
updated_at_copy;
|
||||
|
||||
entity_identifier_copy =
|
||||
node_identifier_copy =
|
||||
NULL;
|
||||
|
||||
updated_at_copy =
|
||||
|
|
@ -271,7 +271,7 @@ cleanup:
|
|||
);
|
||||
|
||||
g_free(
|
||||
entity_identifier_copy
|
||||
node_identifier_copy
|
||||
);
|
||||
|
||||
return position;
|
||||
|
|
@ -291,7 +291,7 @@ void graph_node_position_free(
|
|||
);
|
||||
|
||||
g_free(
|
||||
position->entity_identifier
|
||||
position->node_identifier
|
||||
);
|
||||
|
||||
g_free(
|
||||
|
|
@ -299,12 +299,12 @@ void graph_node_position_free(
|
|||
);
|
||||
}
|
||||
|
||||
const char *graph_node_position_get_entity_identifier(
|
||||
const char *graph_node_position_get_node_identifier(
|
||||
const GraphNodePosition *position
|
||||
)
|
||||
{
|
||||
return position != NULL
|
||||
? position->entity_identifier
|
||||
? position->node_identifier
|
||||
: NULL;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#include <math.h>
|
||||
|
||||
/**
|
||||
* @brief Coordonnées privées associées à une entité.
|
||||
* @brief Coordonnées privées associées à un nœud.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
|
|
@ -22,7 +22,7 @@ typedef struct
|
|||
*/
|
||||
struct InvestigationGraphLayout
|
||||
{
|
||||
GHashTable *positions_by_entity_identifier;
|
||||
GHashTable *positions_by_node_identifier;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -48,16 +48,16 @@ static void investigation_graph_layout_set_error_literal(
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Vérifie un identifiant d'entité.
|
||||
* @brief Vérifie un identifiant de nœud.
|
||||
*/
|
||||
static gboolean investigation_graph_layout_identifier_is_valid(
|
||||
const char *entity_identifier
|
||||
const char *node_identifier
|
||||
)
|
||||
{
|
||||
return entity_identifier != NULL &&
|
||||
entity_identifier[0] != '\0' &&
|
||||
return node_identifier != NULL &&
|
||||
node_identifier[0] != '\0' &&
|
||||
g_uuid_string_is_valid(
|
||||
entity_identifier
|
||||
node_identifier
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ InvestigationGraphLayout *investigation_graph_layout_new(void)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
layout->positions_by_entity_identifier =
|
||||
layout->positions_by_node_identifier =
|
||||
g_hash_table_new_full(
|
||||
g_str_hash,
|
||||
g_str_equal,
|
||||
|
|
@ -92,7 +92,7 @@ InvestigationGraphLayout *investigation_graph_layout_new(void)
|
|||
g_free
|
||||
);
|
||||
|
||||
if (layout->positions_by_entity_identifier == NULL)
|
||||
if (layout->positions_by_node_identifier == NULL)
|
||||
{
|
||||
investigation_graph_layout_free(
|
||||
layout
|
||||
|
|
@ -106,7 +106,7 @@ InvestigationGraphLayout *investigation_graph_layout_new(void)
|
|||
|
||||
gboolean investigation_graph_layout_set_position(
|
||||
InvestigationGraphLayout *layout,
|
||||
const char *entity_identifier,
|
||||
const char *node_identifier,
|
||||
double x,
|
||||
double y,
|
||||
GError **error
|
||||
|
|
@ -115,7 +115,7 @@ gboolean investigation_graph_layout_set_position(
|
|||
InvestigationGraphLayoutPosition *position =
|
||||
NULL;
|
||||
|
||||
char *entity_identifier_copy =
|
||||
char *node_identifier_copy =
|
||||
NULL;
|
||||
|
||||
g_return_val_if_fail(
|
||||
|
|
@ -124,7 +124,7 @@ gboolean investigation_graph_layout_set_position(
|
|||
);
|
||||
|
||||
if (layout == NULL ||
|
||||
layout->positions_by_entity_identifier == NULL)
|
||||
layout->positions_by_node_identifier == NULL)
|
||||
{
|
||||
investigation_graph_layout_set_error_literal(
|
||||
error,
|
||||
|
|
@ -136,13 +136,13 @@ gboolean investigation_graph_layout_set_position(
|
|||
}
|
||||
|
||||
if (!investigation_graph_layout_identifier_is_valid(
|
||||
entity_identifier
|
||||
node_identifier
|
||||
))
|
||||
{
|
||||
investigation_graph_layout_set_error_literal(
|
||||
error,
|
||||
INVESTIGATION_GRAPH_LAYOUT_ERROR_INVALID_IDENTIFIER,
|
||||
"L'identifiant de l'entité n'est pas un UUID valide."
|
||||
"L'identifiant du nœud n'est pas un UUID valide."
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
|
|
@ -160,9 +160,9 @@ gboolean investigation_graph_layout_set_position(
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
entity_identifier_copy =
|
||||
node_identifier_copy =
|
||||
g_strdup(
|
||||
entity_identifier
|
||||
node_identifier
|
||||
);
|
||||
|
||||
position =
|
||||
|
|
@ -171,7 +171,7 @@ gboolean investigation_graph_layout_set_position(
|
|||
1
|
||||
);
|
||||
|
||||
if (entity_identifier_copy == NULL ||
|
||||
if (node_identifier_copy == NULL ||
|
||||
position == NULL)
|
||||
{
|
||||
g_free(
|
||||
|
|
@ -179,7 +179,7 @@ gboolean investigation_graph_layout_set_position(
|
|||
);
|
||||
|
||||
g_free(
|
||||
entity_identifier_copy
|
||||
node_identifier_copy
|
||||
);
|
||||
|
||||
investigation_graph_layout_set_error_literal(
|
||||
|
|
@ -198,8 +198,8 @@ gboolean investigation_graph_layout_set_position(
|
|||
y;
|
||||
|
||||
g_hash_table_replace(
|
||||
layout->positions_by_entity_identifier,
|
||||
entity_identifier_copy,
|
||||
layout->positions_by_node_identifier,
|
||||
node_identifier_copy,
|
||||
position
|
||||
);
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ gboolean investigation_graph_layout_set_position(
|
|||
|
||||
gboolean investigation_graph_layout_get_position(
|
||||
const InvestigationGraphLayout *layout,
|
||||
const char *entity_identifier,
|
||||
const char *node_identifier,
|
||||
double *x,
|
||||
double *y
|
||||
)
|
||||
|
|
@ -217,9 +217,9 @@ gboolean investigation_graph_layout_get_position(
|
|||
NULL;
|
||||
|
||||
if (layout == NULL ||
|
||||
layout->positions_by_entity_identifier == NULL ||
|
||||
layout->positions_by_node_identifier == NULL ||
|
||||
!investigation_graph_layout_identifier_is_valid(
|
||||
entity_identifier
|
||||
node_identifier
|
||||
))
|
||||
{
|
||||
return FALSE;
|
||||
|
|
@ -227,8 +227,8 @@ gboolean investigation_graph_layout_get_position(
|
|||
|
||||
position =
|
||||
g_hash_table_lookup(
|
||||
layout->positions_by_entity_identifier,
|
||||
entity_identifier
|
||||
layout->positions_by_node_identifier,
|
||||
node_identifier
|
||||
);
|
||||
|
||||
if (position == NULL)
|
||||
|
|
@ -253,21 +253,21 @@ gboolean investigation_graph_layout_get_position(
|
|||
|
||||
gboolean investigation_graph_layout_remove_position(
|
||||
InvestigationGraphLayout *layout,
|
||||
const char *entity_identifier
|
||||
const char *node_identifier
|
||||
)
|
||||
{
|
||||
if (layout == NULL ||
|
||||
layout->positions_by_entity_identifier == NULL ||
|
||||
layout->positions_by_node_identifier == NULL ||
|
||||
!investigation_graph_layout_identifier_is_valid(
|
||||
entity_identifier
|
||||
node_identifier
|
||||
))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return g_hash_table_remove(
|
||||
layout->positions_by_entity_identifier,
|
||||
entity_identifier
|
||||
layout->positions_by_node_identifier,
|
||||
node_identifier
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -276,13 +276,13 @@ void investigation_graph_layout_clear(
|
|||
)
|
||||
{
|
||||
if (layout == NULL ||
|
||||
layout->positions_by_entity_identifier == NULL)
|
||||
layout->positions_by_node_identifier == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_remove_all(
|
||||
layout->positions_by_entity_identifier
|
||||
layout->positions_by_node_identifier
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -291,13 +291,13 @@ guint investigation_graph_layout_get_count(
|
|||
)
|
||||
{
|
||||
if (layout == NULL ||
|
||||
layout->positions_by_entity_identifier == NULL)
|
||||
layout->positions_by_node_identifier == NULL)
|
||||
{
|
||||
return 0U;
|
||||
}
|
||||
|
||||
return g_hash_table_size(
|
||||
layout->positions_by_entity_identifier
|
||||
layout->positions_by_node_identifier
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -311,7 +311,7 @@ void investigation_graph_layout_free(
|
|||
}
|
||||
|
||||
g_clear_pointer(
|
||||
&layout->positions_by_entity_identifier,
|
||||
&layout->positions_by_node_identifier,
|
||||
g_hash_table_unref
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1170,10 +1170,10 @@ gboolean create_relation_dialog_present(
|
|||
)
|
||||
);
|
||||
|
||||
g_object_unref(
|
||||
target_labels
|
||||
);
|
||||
|
||||
/*
|
||||
* Le constructeur prend possession de la référence du modèle.
|
||||
* Le dialogue ne doit donc pas appeler g_object_unref() ici.
|
||||
*/
|
||||
target_labels =
|
||||
NULL;
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -156,7 +156,7 @@ static void test_graph_node_position_dao_fixture_clear(
|
|||
*/
|
||||
static void test_graph_node_position_dao_insert_entity(
|
||||
Database *database,
|
||||
const char *entity_identifier,
|
||||
const char *node_identifier,
|
||||
const char *entity_value
|
||||
)
|
||||
{
|
||||
|
|
@ -168,7 +168,7 @@ static void test_graph_node_position_dao_insert_entity(
|
|||
);
|
||||
|
||||
g_assert_nonnull(
|
||||
entity_identifier
|
||||
node_identifier
|
||||
);
|
||||
|
||||
g_assert_nonnull(
|
||||
|
|
@ -208,7 +208,7 @@ static void test_graph_node_position_dao_insert_entity(
|
|||
database_statement_bind_text(
|
||||
statement,
|
||||
1,
|
||||
entity_identifier
|
||||
node_identifier
|
||||
)
|
||||
);
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ static int64_t test_graph_node_position_dao_count_rows(
|
|||
database_statement_prepare(
|
||||
database,
|
||||
"SELECT COUNT(*) "
|
||||
"FROM graph_node_positions;"
|
||||
"FROM graph_layout_positions;"
|
||||
);
|
||||
|
||||
g_assert_nonnull(
|
||||
|
|
@ -454,19 +454,16 @@ static void test_graph_node_position_dao_upsert_insert(void)
|
|||
GError *error =
|
||||
NULL;
|
||||
|
||||
const char *entity_identifier =
|
||||
const char *node_identifier =
|
||||
"10000000-0000-4000-8000-000000000001";
|
||||
|
||||
test_graph_node_position_dao_insert_entity(
|
||||
fixture.database,
|
||||
entity_identifier,
|
||||
"entite-position-1"
|
||||
);
|
||||
/* Un nœud générique peut être une relation et n'a pas à exister dans
|
||||
* la table entites. */
|
||||
|
||||
g_assert_true(
|
||||
graph_node_position_dao_upsert(
|
||||
fixture.position_dao,
|
||||
entity_identifier,
|
||||
node_identifier,
|
||||
120.5,
|
||||
-42.25,
|
||||
&error
|
||||
|
|
@ -516,11 +513,11 @@ static void test_graph_node_position_dao_upsert_insert(void)
|
|||
);
|
||||
|
||||
g_assert_cmpstr(
|
||||
graph_node_position_get_entity_identifier(
|
||||
graph_node_position_get_node_identifier(
|
||||
position
|
||||
),
|
||||
==,
|
||||
entity_identifier
|
||||
node_identifier
|
||||
);
|
||||
|
||||
g_assert_cmpfloat(
|
||||
|
|
@ -578,19 +575,19 @@ static void test_graph_node_position_dao_upsert_update(void)
|
|||
GError *error =
|
||||
NULL;
|
||||
|
||||
const char *entity_identifier =
|
||||
const char *node_identifier =
|
||||
"20000000-0000-4000-8000-000000000002";
|
||||
|
||||
test_graph_node_position_dao_insert_entity(
|
||||
fixture.database,
|
||||
entity_identifier,
|
||||
node_identifier,
|
||||
"entite-position-2"
|
||||
);
|
||||
|
||||
g_assert_true(
|
||||
graph_node_position_dao_upsert(
|
||||
fixture.position_dao,
|
||||
entity_identifier,
|
||||
node_identifier,
|
||||
10.0,
|
||||
20.0,
|
||||
&error
|
||||
|
|
@ -604,7 +601,7 @@ static void test_graph_node_position_dao_upsert_update(void)
|
|||
g_assert_true(
|
||||
graph_node_position_dao_upsert(
|
||||
fixture.position_dao,
|
||||
entity_identifier,
|
||||
node_identifier,
|
||||
-300.75,
|
||||
450.5,
|
||||
&error
|
||||
|
|
@ -855,7 +852,7 @@ static void test_graph_node_position_dao_list_order(void)
|
|||
);
|
||||
|
||||
g_assert_cmpstr(
|
||||
graph_node_position_get_entity_identifier(
|
||||
graph_node_position_get_node_identifier(
|
||||
first_position
|
||||
),
|
||||
==,
|
||||
|
|
@ -863,7 +860,7 @@ static void test_graph_node_position_dao_list_order(void)
|
|||
);
|
||||
|
||||
g_assert_cmpstr(
|
||||
graph_node_position_get_entity_identifier(
|
||||
graph_node_position_get_node_identifier(
|
||||
second_position
|
||||
),
|
||||
==,
|
||||
|
|
@ -887,19 +884,19 @@ static void test_graph_node_position_dao_delete_existing(void)
|
|||
GError *error =
|
||||
NULL;
|
||||
|
||||
const char *entity_identifier =
|
||||
const char *node_identifier =
|
||||
"60000000-0000-4000-8000-000000000006";
|
||||
|
||||
test_graph_node_position_dao_insert_entity(
|
||||
fixture.database,
|
||||
entity_identifier,
|
||||
node_identifier,
|
||||
"entite-position-6"
|
||||
);
|
||||
|
||||
g_assert_true(
|
||||
graph_node_position_dao_upsert(
|
||||
fixture.position_dao,
|
||||
entity_identifier,
|
||||
node_identifier,
|
||||
60.0,
|
||||
70.0,
|
||||
&error
|
||||
|
|
@ -913,7 +910,7 @@ static void test_graph_node_position_dao_delete_existing(void)
|
|||
g_assert_true(
|
||||
graph_node_position_dao_delete(
|
||||
fixture.position_dao,
|
||||
entity_identifier,
|
||||
node_identifier,
|
||||
&error
|
||||
)
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue