Compare commits

..

No commits in common. "6b43379e75784b2b87add980ad2e8ecd7da0dd7a" and "8adfc3f10620e6c26e6f190973cb08fdc9939f10" have entirely different histories.

26 changed files with 65 additions and 1511 deletions

View file

@ -2,9 +2,6 @@
### Added ### Added
- Glisser-déposer confirmé des extractions texte vers le graphe, avec
persistance de leur association à une entité.
- Initialisation du cycle de vie de l'application. - Initialisation du cycle de vie de l'application.
- Création du module `Application`. - Création du module `Application`.
- Intégration de GTK4. - Intégration de GTK4.

View file

@ -128,7 +128,6 @@ TEST_EML_ANALYZER := tests/test_eml_analyzer
TEST_IBAN_ANALYZER := tests/test_iban_analyzer TEST_IBAN_ANALYZER := tests/test_iban_analyzer
TEST_EXIFTOOL_METADATA := tests/test_exiftool_metadata TEST_EXIFTOOL_METADATA := tests/test_exiftool_metadata
TEST_PDF_PASSWORD_RECOVERY := tests/test_pdf_password_recovery TEST_PDF_PASSWORD_RECOVERY := tests/test_pdf_password_recovery
TEST_EXTRACTION_DROP_SERVICE := tests/test_extraction_drop_service
all: $(TARGET) all: $(TARGET)
@ -198,22 +197,6 @@ $(TEST_ERROR): \
src/database/error.c src/database/error.c
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
$(TEST_EXTRACTION_DROP_SERVICE): \
tests/test_extraction_drop_service.c \
src/core/extraction_drop_service.c \
src/core/file_hash.c \
src/dao/evidence_dao.c \
src/dao/entity_dao.c \
src/dao/evidence_entity_dao.c \
src/models/evidence_record.c \
src/models/entity_record.c \
src/database/database.c \
src/database/schema.c \
src/database/statement.c \
src/database/transaction.c \
src/database/error.c
$(CC) $(TEST_CFLAGS) -Wpedantic $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
$(TEST_INVESTIGATION_RECORD): \ $(TEST_INVESTIGATION_RECORD): \
tests/test_investigation_record.c \ tests/test_investigation_record.c \
src/models/investigation_record.c src/models/investigation_record.c
@ -763,8 +746,7 @@ test: \
$(TEST_EML_ANALYZER) \ $(TEST_EML_ANALYZER) \
$(TEST_IBAN_ANALYZER) \ $(TEST_IBAN_ANALYZER) \
$(TEST_EXIFTOOL_METADATA) \ $(TEST_EXIFTOOL_METADATA) \
$(TEST_PDF_PASSWORD_RECOVERY) \ $(TEST_PDF_PASSWORD_RECOVERY)
$(TEST_EXTRACTION_DROP_SERVICE)
@echo "Exécution des tests..." @echo "Exécution des tests..."
@./$(TEST_NODE) @./$(TEST_NODE)
@./$(TEST_TREE_MODEL) @./$(TEST_TREE_MODEL)
@ -827,7 +809,6 @@ test: \
@$(TEST_IBAN_ANALYZER) @$(TEST_IBAN_ANALYZER)
@$(TEST_EXIFTOOL_METADATA) @$(TEST_EXIFTOOL_METADATA)
@$(TEST_PDF_PASSWORD_RECOVERY) @$(TEST_PDF_PASSWORD_RECOVERY)
@$(TEST_EXTRACTION_DROP_SERVICE)
@echo "Tous les tests sont valides." @echo "Tous les tests sont valides."
%.o: %.c %.o: %.c
@ -893,8 +874,7 @@ clean:
$(TEST_SOCIAL_ACCOUNT_SERVICE) \ $(TEST_SOCIAL_ACCOUNT_SERVICE) \
$(TEST_SOCIAL_PLATFORM) \ $(TEST_SOCIAL_PLATFORM) \
$(TEST_PERSON_ENTITY_SERVICE) \ $(TEST_PERSON_ENTITY_SERVICE) \
$(TEST_EML_ANALYZER) \ $(TEST_EML_ANALYZER)
$(TEST_EXTRACTION_DROP_SERVICE)
-include $(DEP) -include $(DEP)

View file

@ -173,9 +173,6 @@ Le socle actuel comprend notamment :
sorties standard et d'erreur, et objets créés ou réutilisés ; sorties standard et d'erreur, et objets créés ou réutilisés ;
- vérification manuelle de l'intégrité des sorties OSINT enregistrées, sans - vérification manuelle de l'intégrité des sorties OSINT enregistrées, sans
réécriture de l'empreinte ou des données contrôlées. réécriture de l'empreinte ou des données contrôlées.
- glisser-déposer des extractions texte depuis l'arborescence vers le graphe,
avec confirmation explicite, création d'entité ou rattachement à une entité
existante, sans déplacement du fichier produit ;
Les outils actuellement présents dans le catalogue initial sont : Les outils actuellement présents dans le catalogue initial sont :

45
TICKET-105.md Normal file
View file

@ -0,0 +1,45 @@
# Ticket 105 — Glisser-déposer des extractions vers le graphe
## Objectif
Permettre de prendre une extraction depuis `Dossier → Extractions` et de la
déposer dans le graphe afin de créer ou compléter les liens de lenquête.
## Fonctionnement attendu
1. Une extraction sélectionnée dans la Sidebar peut être déplacée.
2. Le graphe accepte le dépôt uniquement sur une zone valide.
3. Une fenêtre de confirmation affiche le fichier, sa source et son contenu
textuel disponible.
4. Lutilisateur choisit explicitement :
- créer une nouvelle entité à partir de lextraction ;
- rattacher lextraction à une entité existante ;
- annuler.
5. Lassociation est enregistrée en base et visible dans la fiche concernée.
6. Une relation graphique peut être créée sans déplacer ni dupliquer le
fichier original.
## Contraintes
- Ne jamais créer de relation silencieusement.
- Refuser les fichiers hors du dossier de lenquête.
- Conserver les associations déjà présentes.
- Utiliser les API GTK de glisser-déposer et les commentaires Doxygen prévus
par le projet.
## Critères dacceptation
- Un fichier `.txt` dOSINT est déplaçable depuis la Sidebar.
- Le dépôt sur le graphe ouvre le dialogue de choix.
- Lentité ou lassociation créée survit à la réouverture de lenquête.
- Les dépôts annulés ne modifient ni la base ni le graphe.
- Tests unitaires des chemins invalides, du choix daction et de la
persistance.
## Vérification
```sh
make -j2
make test
```

View file

@ -1,7 +1,7 @@
/****************************************************************************** /******************************************************************************
* Labfy Investigation * Labfy Investigation
* *
* Extensions idempotentes du schéma SQLite courant V8 * Extensions idempotentes du schéma SQLite courant V6
******************************************************************************/ ******************************************************************************/
/* /*
@ -93,15 +93,6 @@ CREATE TABLE IF NOT EXISTS graph_layout_positions
CHECK (length(updated_at) = 20) CHECK (length(updated_at) = 20)
); );
CREATE TABLE IF NOT EXISTS graph_viewport
(
id INTEGER PRIMARY KEY CHECK (id = 1),
zoom REAL NOT NULL CHECK (zoom = zoom AND zoom > 0),
offset_x REAL NOT NULL CHECK (offset_x = offset_x),
offset_y REAL NOT NULL CHECK (offset_y = offset_y),
updated_at TEXT NOT NULL CHECK (length(updated_at) = 20)
);
/* Migration idempotente des positions d'entités déjà enregistrées. */ /* Migration idempotente des positions d'entités déjà enregistrées. */
INSERT OR IGNORE INTO graph_layout_positions(node_id, x, y, updated_at) INSERT OR IGNORE INTO graph_layout_positions(node_id, x, y, updated_at)
SELECT entity_id, x, y, updated_at SELECT entity_id, x, y, updated_at

View file

@ -1,8 +0,0 @@
CREATE TABLE IF NOT EXISTS graph_viewport
(
id INTEGER PRIMARY KEY CHECK (id = 1),
zoom REAL NOT NULL CHECK (zoom = zoom AND zoom > 0),
offset_x REAL NOT NULL CHECK (offset_x = offset_x),
offset_y REAL NOT NULL CHECK (offset_y = offset_y),
updated_at TEXT NOT NULL CHECK (length(updated_at) = 20)
);

View file

@ -2269,12 +2269,6 @@ 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, 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é. d'enregistrement et de réinitialisation que les nœuds d'entité.
Le cadrage global est stocké séparément dans la ligne unique de
`graph_viewport`. Elle conserve le niveau de zoom et les décalages horizontal
et vertical du canevas. Ces valeurs sont enregistrées à la fermeture de
l'enquête et restaurées lors de son premier chargement, afin que la vue
réapparaisse exactement à l'endroit où l'utilisateur l'avait laissée.
--- ---
# 12. Conclusion # 12. Conclusion

View file

@ -1,76 +0,0 @@
/******************************************************************************
* @file extraction_drop_service.h
* @brief Validation et intégration d'une extraction déposée sur le graphe.
******************************************************************************/
#ifndef LABFY_INVESTIGATION_EXTRACTION_DROP_SERVICE_H
#define LABFY_INVESTIGATION_EXTRACTION_DROP_SERVICE_H
#include "database/database.h"
#include <glib.h>
G_BEGIN_DECLS
typedef struct ExtractionDropInfo ExtractionDropInfo;
typedef enum
{
EXTRACTION_DROP_SERVICE_ERROR_INVALID_ARGUMENT,
EXTRACTION_DROP_SERVICE_ERROR_OUTSIDE_INVESTIGATION,
EXTRACTION_DROP_SERVICE_ERROR_INVALID_FILE,
EXTRACTION_DROP_SERVICE_ERROR_READ,
EXTRACTION_DROP_SERVICE_ERROR_DATABASE
} ExtractionDropServiceError;
#define EXTRACTION_DROP_SERVICE_ERROR extraction_drop_service_error_quark()
GQuark extraction_drop_service_error_quark(void);
/**
* @brief Valide et lit une extraction texte appartenant à l'enquête.
*
* Le fichier doit être un `.txt` régulier situé sous
* `02_Preuves_Traitees/Extractions`. Aucun fichier n'est déplacé ou modifié.
*/
ExtractionDropInfo *extraction_drop_service_prepare(
const char *investigation_root,
const char *file_path,
GError **error
);
void extraction_drop_info_free(ExtractionDropInfo *info);
const char *extraction_drop_info_get_file_path(const ExtractionDropInfo *info);
const char *extraction_drop_info_get_relative_path(
const ExtractionDropInfo *info);
const char *extraction_drop_info_get_file_name(const ExtractionDropInfo *info);
const char *extraction_drop_info_get_source(const ExtractionDropInfo *info);
const char *extraction_drop_info_get_content(const ExtractionDropInfo *info);
/**
* @brief Enregistre l'extraction comme preuve traitée et la lie à une entité.
*
* Une preuve ou une association déjà présente est réutilisée.
*/
gboolean extraction_drop_service_attach(
Database *database,
const ExtractionDropInfo *info,
const char *entity_identifier,
GError **error
);
/**
* @brief Crée une entité générique puis lui associe l'extraction.
*
* @return UUID nouvellement alloué dans out_entity_identifier, si demandé.
*/
gboolean extraction_drop_service_create_entity(
Database *database,
const ExtractionDropInfo *info,
char **out_entity_identifier,
GError **error
);
G_END_DECLS
#endif

View file

@ -143,23 +143,6 @@ gboolean graph_node_position_dao_delete_all(
GError **error GError **error
); );
gboolean graph_node_position_dao_load_viewport(
GraphNodePositionDao *position_dao,
double *zoom,
double *offset_x,
double *offset_y,
gboolean *found,
GError **error
);
gboolean graph_node_position_dao_upsert_viewport(
GraphNodePositionDao *position_dao,
double zoom,
double offset_x,
double offset_y,
GError **error
);
G_END_DECLS G_END_DECLS
#endif #endif

View file

@ -88,7 +88,6 @@ bool schema_install_v5(Database *database);
bool schema_install_v6(Database *database); bool schema_install_v6(Database *database);
bool schema_install_v7(Database *database); bool schema_install_v7(Database *database);
bool schema_install_v8(Database *database);
/** /**
* @brief Garantit la présence des extensions du schéma courant V2. * @brief Garantit la présence des extensions du schéma courant V2.

View file

@ -126,12 +126,6 @@ typedef void (*MainWindowGraphNodeMovedCallback)(
gpointer user_data gpointer user_data
); );
typedef void (*MainWindowExtractionDropCallback)(
const char *file_path,
const char *target_entity_identifier,
gpointer user_data
);
/** /**
* @brief Callback appelé lors d'une demande de réinitialisation du graphe. * @brief Callback appelé lors d'une demande de réinitialisation du graphe.
* *
@ -252,12 +246,6 @@ void main_window_set_graph_node_moved_callback(
gpointer user_data gpointer user_data
); );
void main_window_set_extraction_drop_callback(
MainWindow *main_window,
MainWindowExtractionDropCallback callback,
gpointer user_data
);
/** /**
* @brief Définit le callback de demande de réinitialisation du graphe. * @brief Définit le callback de demande de réinitialisation du graphe.
* *
@ -526,11 +514,6 @@ void main_window_set_graph(
const InvestigationGraphLayout *graph_layout const InvestigationGraphLayout *graph_layout
); );
gboolean main_window_get_graph_view_transform(
MainWindow *main_window, double *zoom, double *offset_x, double *offset_y);
void main_window_set_graph_view_transform(
MainWindow *main_window, double zoom, double offset_x, double offset_y);
/** @brief Sélectionne une relation dans le graphe affiché. */ /** @brief Sélectionne une relation dans le graphe affiché. */
gboolean main_window_select_graph_relation( gboolean main_window_select_graph_relation(
MainWindow *main_window, MainWindow *main_window,

View file

@ -69,9 +69,6 @@ typedef void (*EntityDetailsPanelPersonNameCallback)(
/** @brief Callback appelé pour gérer les preuves d'une personne. */ /** @brief Callback appelé pour gérer les preuves d'une personne. */
typedef void (*EntityDetailsPanelPersonEvidenceCallback)( typedef void (*EntityDetailsPanelPersonEvidenceCallback)(
const char *entity_identifier, gpointer user_data); const char *entity_identifier, gpointer user_data);
/** @brief Callback appelé lorsqu'une preuve associée est activée. */
typedef void (*EntityDetailsPanelEvidenceActivatedCallback)(
const char *evidence_identifier, gpointer user_data);
/** @brief Callback appelé pour ouvrir les recherches OSINT compatibles. */ /** @brief Callback appelé pour ouvrir les recherches OSINT compatibles. */
typedef void (*EntityDetailsPanelOsintCallback)( typedef void (*EntityDetailsPanelOsintCallback)(
const char *entity_identifier, gpointer user_data); const char *entity_identifier, gpointer user_data);
@ -181,10 +178,6 @@ void entity_details_panel_set_person_name_callback(
void entity_details_panel_set_person_evidence_callback( void entity_details_panel_set_person_evidence_callback(
EntityDetailsPanel *details_panel, EntityDetailsPanel *details_panel,
EntityDetailsPanelPersonEvidenceCallback callback, gpointer user_data); EntityDetailsPanelPersonEvidenceCallback callback, gpointer user_data);
void entity_details_panel_set_evidence_activated_callback(
EntityDetailsPanel *details_panel,
EntityDetailsPanelEvidenceActivatedCallback callback,
gpointer user_data);
/** @brief Définit le callback du bouton de recherche OSINT. */ /** @brief Définit le callback du bouton de recherche OSINT. */
void entity_details_panel_set_osint_callback(EntityDetailsPanel *details_panel, void entity_details_panel_set_osint_callback(EntityDetailsPanel *details_panel,
EntityDetailsPanelOsintCallback callback, gpointer user_data); EntityDetailsPanelOsintCallback callback, gpointer user_data);

View file

@ -71,13 +71,6 @@ typedef void (*InvestigationGraphViewNodeMovedCallback)(
gpointer user_data gpointer user_data
); );
/** @brief Callback appelé lorsqu'une extraction est déposée sur le graphe. */
typedef void (*InvestigationGraphViewExtractionDropCallback)(
const char *file_path,
const char *target_entity_identifier,
gpointer user_data
);
/** /**
* @brief Crée une nouvelle vue graphique vide. * @brief Crée une nouvelle vue graphique vide.
* *
@ -177,12 +170,6 @@ void investigation_graph_view_set_node_moved_callback(
gpointer user_data gpointer user_data
); );
void investigation_graph_view_set_extraction_drop_callback(
InvestigationGraphView *graph_view,
InvestigationGraphViewExtractionDropCallback callback,
gpointer user_data
);
/** /**
* @brief Retourne l'entité actuellement sélectionnée. * @brief Retourne l'entité actuellement sélectionnée.
* *
@ -247,26 +234,6 @@ void investigation_graph_view_reset_view(
InvestigationGraphView *graph_view InvestigationGraphView *graph_view
); );
/**
* @brief Lit le zoom et le déplacement courants du canvas.
*
* @return TRUE lorsqu'un graphe est actuellement présenté.
*/
gboolean investigation_graph_view_get_view_transform(
const InvestigationGraphView *graph_view,
double *zoom,
double *offset_x,
double *offset_y
);
/** @brief Restaure un zoom et un déplacement précédemment lus. */
void investigation_graph_view_set_view_transform(
InvestigationGraphView *graph_view,
double zoom,
double offset_x,
double offset_y
);
/** /**
* @brief Restaure la disposition déterministe initiale des nœuds. * @brief Restaure la disposition déterministe initiale des nœuds.
* *

View file

@ -56,12 +56,6 @@ typedef void (*WorkspaceGraphNodeMovedCallback)(
gpointer user_data gpointer user_data
); );
typedef void (*WorkspaceExtractionDropCallback)(
const char *file_path,
const char *target_entity_identifier,
gpointer user_data
);
/** /**
* @brief Callback appelé lors d'une demande de réinitialisation du graphe. * @brief Callback appelé lors d'une demande de réinitialisation du graphe.
* *
@ -129,8 +123,6 @@ typedef void (*WorkspacePersonNameCallback)(const char *entity_identifier,
/** @brief Callback appelé pour gérer les preuves d'une personne. */ /** @brief Callback appelé pour gérer les preuves d'une personne. */
typedef void (*WorkspacePersonEvidenceCallback)( typedef void (*WorkspacePersonEvidenceCallback)(
const char *entity_identifier, gpointer user_data); const char *entity_identifier, gpointer user_data);
typedef void (*WorkspaceEvidenceActivatedCallback)(
const char *evidence_identifier, gpointer user_data);
/** @brief Callback appelé quand une entité devient sélectionnée. */ /** @brief Callback appelé quand une entité devient sélectionnée. */
typedef void (*WorkspaceEntitySelectedCallback)( typedef void (*WorkspaceEntitySelectedCallback)(
const char *entity_identifier, gpointer user_data); const char *entity_identifier, gpointer user_data);
@ -325,12 +317,6 @@ void workspace_set_graph_node_moved_callback(
gpointer user_data gpointer user_data
); );
void workspace_set_extraction_drop_callback(
Workspace *workspace,
WorkspaceExtractionDropCallback callback,
gpointer user_data
);
/** /**
* @brief Définit le callback de demande de réinitialisation du graphe. * @brief Définit le callback de demande de réinitialisation du graphe.
* *
@ -418,10 +404,6 @@ void workspace_set_person_name_callback(Workspace *workspace,
/** @brief Définit le callback de gestion des preuves d'une personne. */ /** @brief Définit le callback de gestion des preuves d'une personne. */
void workspace_set_person_evidence_callback(Workspace *workspace, void workspace_set_person_evidence_callback(Workspace *workspace,
WorkspacePersonEvidenceCallback callback, gpointer user_data); WorkspacePersonEvidenceCallback callback, gpointer user_data);
void workspace_set_evidence_activated_callback(
Workspace *workspace,
WorkspaceEvidenceActivatedCallback callback,
gpointer user_data);
/** @brief Définit le callback de sélection d'une entité. */ /** @brief Définit le callback de sélection d'une entité. */
void workspace_set_entity_selected_callback(Workspace *workspace, void workspace_set_entity_selected_callback(Workspace *workspace,
WorkspaceEntitySelectedCallback callback, gpointer user_data); WorkspaceEntitySelectedCallback callback, gpointer user_data);
@ -447,11 +429,6 @@ void workspace_set_graph(
const InvestigationGraphLayout *graph_layout const InvestigationGraphLayout *graph_layout
); );
gboolean workspace_get_graph_view_transform(
Workspace *workspace, double *zoom, double *offset_x, double *offset_y);
void workspace_set_graph_view_transform(
Workspace *workspace, double zoom, double offset_x, double offset_y);
/** /**
* @brief Affiche une erreur de chargement du graphe. * @brief Affiche une erreur de chargement du graphe.
* *

View file

@ -67,7 +67,6 @@
#include "core/exiftool_metadata.h" #include "core/exiftool_metadata.h"
#include "views/metadata_analysis_dialog.h" #include "views/metadata_analysis_dialog.h"
#include "core/pdf_password_recovery.h" #include "core/pdf_password_recovery.h"
#include "core/extraction_drop_service.h"
#include "views/pdf_password_dialog.h" #include "views/pdf_password_dialog.h"
#include <gtk/gtk.h> #include <gtk/gtk.h>
@ -137,7 +136,6 @@ struct Application
InvestigationGraphModel *graph_model; InvestigationGraphModel *graph_model;
InvestigationGraphLayout *graph_layout; InvestigationGraphLayout *graph_layout;
guint64 graph_load_generation; guint64 graph_load_generation;
gboolean graph_viewport_restored;
char *selected_evidence_identifier; char *selected_evidence_identifier;
char *pending_relation_selection_identifier; char *pending_relation_selection_identifier;
@ -227,13 +225,6 @@ typedef struct
char *started_at; char *started_at;
} ApplicationOsintActionContext; } ApplicationOsintActionContext;
typedef struct
{
Application *application;
ExtractionDropInfo *info;
char *target_entity_identifier;
} ApplicationExtractionDropContext;
/** @brief Données possédées par l'étape de révision OSINT. */ /** @brief Données possédées par l'étape de révision OSINT. */
typedef struct typedef struct
{ {
@ -253,196 +244,12 @@ static void application_start_graph_loading(
const char *database_path const char *database_path
); );
static void application_save_graph_viewport(Application *application)
{
GraphNodePositionDao *dao = NULL;
GError *error = NULL;
double zoom = 0.0;
double offset_x = 0.0;
double offset_y = 0.0;
if (application == NULL || application->session == NULL ||
application->main_window == NULL ||
!main_window_get_graph_view_transform(application->main_window,
&zoom, &offset_x, &offset_y))
return;
dao = graph_node_position_dao_new(
investigation_session_get_database(application->session), &error);
if (dao == NULL || !graph_node_position_dao_upsert_viewport(
dao, zoom, offset_x, offset_y, &error))
g_warning("Impossible d'enregistrer le cadrage du graphe : %s",
error != NULL ? error->message : "erreur inconnue");
graph_node_position_dao_free(dao);
g_clear_error(&error);
}
static void application_restore_graph_viewport(Application *application)
{
GraphNodePositionDao *dao = NULL;
GError *error = NULL;
gboolean found = FALSE;
double zoom = 0.0;
double offset_x = 0.0;
double offset_y = 0.0;
if (application == NULL || application->graph_viewport_restored ||
application->session == NULL || application->main_window == NULL)
return;
dao = graph_node_position_dao_new(
investigation_session_get_database(application->session), &error);
if (dao != NULL && graph_node_position_dao_load_viewport(
dao, &zoom, &offset_x, &offset_y, &found, &error) && found)
main_window_set_graph_view_transform(
application->main_window, zoom, offset_x, offset_y);
else if (error != NULL)
g_warning("Impossible de restaurer le cadrage du graphe : %s",
error->message);
application->graph_viewport_restored = TRUE;
graph_node_position_dao_free(dao);
g_clear_error(&error);
}
static void application_present_error( static void application_present_error(
Application *application, Application *application,
const char *title, const char *title,
const char *message const char *message
); );
static void application_extraction_drop_context_free(
ApplicationExtractionDropContext *context)
{
if (context == NULL) return;
extraction_drop_info_free(context->info);
g_free(context->target_entity_identifier);
g_free(context);
}
static void application_on_extraction_choice_finished(
GObject *source_object, GAsyncResult *result, gpointer user_data)
{
ApplicationExtractionDropContext *context = user_data;
Application *application = context != NULL ? context->application : NULL;
GtkAlertDialog *dialog = GTK_ALERT_DIALOG(source_object);
const InvestigationProject *project = NULL;
Database *database = NULL;
GError *error = NULL;
int choice = gtk_alert_dialog_choose_finish(dialog, result, &error);
gboolean success = FALSE;
if (application == NULL || application->session == NULL) goto cleanup;
if (error != NULL || choice == 0)
{
main_window_set_status(application->main_window,
"Dépôt de l'extraction annulé.");
goto cleanup;
}
database = investigation_session_get_database(application->session);
if (choice == 1)
success = extraction_drop_service_create_entity(database,
context->info, NULL, &error);
else if (choice == 2 && context->target_entity_identifier != NULL)
success = extraction_drop_service_attach(database, context->info,
context->target_entity_identifier, &error);
else
g_set_error_literal(&error, EXTRACTION_DROP_SERVICE_ERROR,
EXTRACTION_DROP_SERVICE_ERROR_INVALID_ARGUMENT,
"Déposez l'extraction directement sur une entité pour la rattacher.");
if (!success)
{
application_present_error(application,
"Intégration de l'extraction impossible",
error != NULL ? error->message : "L'association n'a pas été créée.");
goto cleanup;
}
main_window_set_status(application->main_window,
choice == 1 ? "Entité créée depuis l'extraction."
: "Extraction rattachée à l'entité.");
project = investigation_session_get_project(application->session);
if (project != NULL)
application_start_graph_loading(application,
investigation_project_get_database_path(project));
cleanup:
g_clear_error(&error);
application_extraction_drop_context_free(context);
}
static void application_on_extraction_dropped(
const char *file_path,
const char *target_entity_identifier,
gpointer user_data)
{
Application *application = user_data;
ApplicationExtractionDropContext *context = NULL;
const InvestigationProject *project = NULL;
GtkAlertDialog *dialog = NULL;
const char *buttons[] = {
"Annuler", "Créer une entité", "Rattacher à l'entité", NULL
};
char *details = NULL;
char *content_preview = NULL;
GError *error = NULL;
if (application == NULL || application->session == NULL) return;
project = investigation_session_get_project(application->session);
context = g_new0(ApplicationExtractionDropContext, 1);
context->application = application;
context->target_entity_identifier = g_strdup(target_entity_identifier);
context->info = extraction_drop_service_prepare(
investigation_project_get_root_path(project), file_path, &error);
if (context->info == NULL)
{
application_present_error(application, "Dépôt refusé",
error != NULL ? error->message : "Extraction invalide.");
g_clear_error(&error);
application_extraction_drop_context_free(context);
return;
}
{
const char *content = extraction_drop_info_get_content(context->info);
const char *preview_end = content;
guint line_count = 0U;
while (preview_end != NULL && *preview_end != '\0' &&
(gsize) (preview_end - content) < 800U &&
line_count < 12U)
{
if (*preview_end == '\n') line_count++;
preview_end = g_utf8_next_char(preview_end);
}
content_preview = g_strndup(content,
preview_end != NULL ? (gsize) (preview_end - content) : 0U);
if (preview_end != NULL && *preview_end != '\0')
{
char *truncated_preview = g_strconcat(content_preview,
"\n\n[… aperçu tronqué, le fichier complet sera conservé]",
NULL);
g_free(content_preview);
content_preview = truncated_preview;
}
}
details = g_strdup_printf("Fichier : %s\nSource : %s\n\n%s",
extraction_drop_info_get_file_name(context->info),
extraction_drop_info_get_source(context->info),
content_preview != NULL ? content_preview : "");
dialog = gtk_alert_dialog_new("Intégrer cette extraction au graphe ?");
gtk_alert_dialog_set_detail(dialog, details);
gtk_alert_dialog_set_buttons(dialog, buttons);
gtk_alert_dialog_set_cancel_button(dialog, 0);
gtk_alert_dialog_set_default_button(dialog, 0);
gtk_alert_dialog_choose(dialog,
main_window_get_window(application->main_window), NULL,
application_on_extraction_choice_finished, context);
g_object_unref(dialog);
g_free(details);
g_free(content_preview);
}
static void application_present_next_batch_metadata( static void application_present_next_batch_metadata(
ApplicationEvidenceBatchContext *context ApplicationEvidenceBatchContext *context
); );
@ -1122,8 +929,6 @@ static void application_on_graph_loaded(
application->graph_layout application->graph_layout
); );
application_restore_graph_viewport(application);
if (application->pending_relation_selection_identifier != NULL) if (application->pending_relation_selection_identifier != NULL)
{ {
main_window_select_graph_relation(application->main_window, main_window_select_graph_relation(application->main_window,
@ -2665,14 +2470,12 @@ static gboolean application_install_session(
application->tree_model application->tree_model
); );
application_save_graph_viewport(application);
investigation_session_close( investigation_session_close(
application->session application->session
); );
application->tree_model = new_tree_model; application->tree_model = new_tree_model;
application->session = new_session; application->session = new_session;
application->graph_viewport_restored = FALSE;
main_window_set_tree_model( main_window_set_tree_model(
application->main_window, application->main_window,
@ -7841,9 +7644,6 @@ static void application_on_activate(
application_on_graph_node_moved, application_on_graph_node_moved,
application application
); );
main_window_set_extraction_drop_callback(
application->main_window, application_on_extraction_dropped,
application);
main_window_set_reset_graph_layout_callback( main_window_set_reset_graph_layout_callback(
application->main_window, application->main_window,
@ -8126,8 +7926,6 @@ void application_free(
application application
); );
application_save_graph_viewport(application);
application_clear_graph( application_clear_graph(
application application
); );

View file

@ -1,361 +0,0 @@
/******************************************************************************
* @file extraction_drop_service.c
* @brief Validation et intégration d'une extraction déposée sur le graphe.
******************************************************************************/
#include "core/extraction_drop_service.h"
#include "core/file_hash.h"
#include "dao/entity_dao.h"
#include "dao/evidence_dao.h"
#include "dao/evidence_entity_dao.h"
#include "database/transaction.h"
#include "models/entity_record.h"
#include "models/evidence_record.h"
#include <string.h>
struct ExtractionDropInfo
{
char *file_path;
char *relative_path;
char *file_name;
char *source;
char *content;
};
GQuark extraction_drop_service_error_quark(void)
{
return g_quark_from_static_string("extraction-drop-service-error");
}
static void extraction_drop_service_set_error(
GError **error,
ExtractionDropServiceError code,
const char *message)
{
if (error != NULL && *error == NULL)
{
g_set_error_literal(error, EXTRACTION_DROP_SERVICE_ERROR, code, message);
}
}
static char *extraction_drop_service_timestamp(void)
{
GDateTime *now = g_date_time_new_now_utc();
char *timestamp = now != NULL
? g_date_time_format(now, "%Y-%m-%dT%H:%M:%SZ") : NULL;
g_clear_pointer(&now, g_date_time_unref);
return timestamp;
}
static char *extraction_drop_service_source_from_content(const char *content)
{
const char *line_end = NULL;
if (content == NULL || content[0] == '\0')
{
return g_strdup("Extraction locale");
}
line_end = strchr(content, '\n');
if (line_end != NULL && line_end > content)
{
return g_strndup(content, (gsize) (line_end - content));
}
return g_strdup(content);
}
ExtractionDropInfo *extraction_drop_service_prepare(
const char *investigation_root,
const char *file_path,
GError **error)
{
ExtractionDropInfo *info = NULL;
char *canonical_root = NULL;
char *extractions_root = NULL;
char *canonical_extractions_root = NULL;
char *canonical_file = NULL;
char *content = NULL;
gsize content_length = 0U;
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
if (investigation_root == NULL || investigation_root[0] == '\0' ||
file_path == NULL || file_path[0] == '\0')
{
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_INVALID_ARGUMENT,
"Le chemin de l'extraction est invalide.");
return NULL;
}
canonical_root = g_canonicalize_filename(investigation_root, NULL);
extractions_root = g_build_filename(canonical_root,
"02_Preuves_Traitees", "Extractions", NULL);
canonical_extractions_root = g_canonicalize_filename(extractions_root, NULL);
canonical_file = g_canonicalize_filename(file_path, NULL);
if (canonical_root == NULL || canonical_extractions_root == NULL ||
canonical_file == NULL)
{
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_INVALID_ARGUMENT,
"Impossible de normaliser le chemin de l'extraction.");
goto cleanup;
}
if (!g_str_has_prefix(canonical_file, canonical_extractions_root) ||
canonical_file[strlen(canonical_extractions_root)] != G_DIR_SEPARATOR)
{
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_OUTSIDE_INVESTIGATION,
"Le fichier ne se trouve pas dans les extractions de l'enquête.");
goto cleanup;
}
if (!g_file_test(canonical_file, G_FILE_TEST_IS_REGULAR) ||
g_file_test(canonical_file, G_FILE_TEST_IS_SYMLINK) ||
!g_str_has_suffix(canonical_file, ".txt"))
{
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_INVALID_FILE,
"Seuls les fichiers texte réguliers d'extraction sont acceptés.");
goto cleanup;
}
if (!g_file_get_contents(canonical_file, &content, &content_length, error))
{
if (error != NULL && *error != NULL)
{
(*error)->domain = EXTRACTION_DROP_SERVICE_ERROR;
(*error)->code = EXTRACTION_DROP_SERVICE_ERROR_READ;
}
goto cleanup;
}
if (!g_utf8_validate(content, (gssize) content_length, NULL))
{
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_INVALID_FILE,
"L'extraction texte n'est pas encodée en UTF-8.");
goto cleanup;
}
info = g_new0(ExtractionDropInfo, 1);
info->file_path = g_strdup(canonical_file);
info->relative_path = g_strdup(canonical_file + strlen(canonical_root) + 1U);
info->file_name = g_path_get_basename(canonical_file);
info->content = g_steal_pointer(&content);
info->source = extraction_drop_service_source_from_content(info->content);
if (info->file_path == NULL || info->relative_path == NULL ||
info->file_name == NULL || info->content == NULL || info->source == NULL)
{
extraction_drop_info_free(info);
info = NULL;
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_INVALID_ARGUMENT,
"Impossible d'allouer les informations de l'extraction.");
}
cleanup:
g_free(content);
g_free(canonical_file);
g_free(canonical_extractions_root);
g_free(extractions_root);
g_free(canonical_root);
return info;
}
void extraction_drop_info_free(ExtractionDropInfo *info)
{
if (info == NULL) return;
g_free(info->file_path);
g_free(info->relative_path);
g_free(info->file_name);
g_free(info->source);
g_free(info->content);
g_free(info);
}
#define EXTRACTION_DROP_GETTER(name, field) \
const char *name(const ExtractionDropInfo *info) \
{ return info != NULL ? info->field : NULL; }
EXTRACTION_DROP_GETTER(extraction_drop_info_get_file_path, file_path)
EXTRACTION_DROP_GETTER(extraction_drop_info_get_relative_path, relative_path)
EXTRACTION_DROP_GETTER(extraction_drop_info_get_file_name, file_name)
EXTRACTION_DROP_GETTER(extraction_drop_info_get_source, source)
EXTRACTION_DROP_GETTER(extraction_drop_info_get_content, content)
static char *extraction_drop_service_ensure_evidence(
Database *database,
const ExtractionDropInfo *info,
GError **error)
{
EvidenceDao *dao = NULL;
EvidenceRecord *record = NULL;
GPtrArray *records = NULL;
char *identifier = NULL;
char *sha256 = NULL;
char *timestamp = NULL;
guint64 size_bytes = 0U;
dao = evidence_dao_new(database, error);
if (dao == NULL) goto cleanup;
records = evidence_dao_list_all(dao, error);
if (records == NULL) goto cleanup;
for (guint index = 0U; index < records->len; index++)
{
const EvidenceRecord *candidate = g_ptr_array_index(records, index);
if (g_strcmp0(evidence_record_get_relative_path(candidate),
info->relative_path) == 0)
{
identifier = g_strdup(evidence_record_get_identifier(candidate));
goto cleanup;
}
}
if (!file_hash_compute_sha256(info->file_path, NULL, &sha256,
&size_bytes, error)) goto cleanup;
identifier = g_uuid_string_random();
timestamp = extraction_drop_service_timestamp();
record = evidence_record_new(identifier, info->file_name, info->file_name,
info->relative_path, "text", size_bytes, sha256, timestamp, NULL,
info->source, "Extraction intégrée depuis le graphe.",
EVIDENCE_INTEGRITY_STATUS_VALID, error);
if (record == NULL || !evidence_dao_insert(dao, record, error))
{
g_clear_pointer(&identifier, g_free);
}
cleanup:
evidence_record_free(record);
g_clear_pointer(&records, g_ptr_array_unref);
evidence_dao_free(dao);
g_free(timestamp);
g_free(sha256);
return identifier;
}
static gboolean extraction_drop_service_attach_in_transaction(
Database *database,
const ExtractionDropInfo *info,
const char *entity_identifier,
GError **error)
{
EvidenceEntityDao *link_dao = NULL;
char *evidence_identifier = NULL;
gboolean exists = FALSE;
gboolean success = FALSE;
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if (database == NULL || info == NULL || entity_identifier == NULL ||
!g_uuid_string_is_valid(entity_identifier))
{
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_INVALID_ARGUMENT,
"L'entité de destination est invalide.");
return FALSE;
}
evidence_identifier = extraction_drop_service_ensure_evidence(database,
info, error);
if (evidence_identifier == NULL) goto cleanup;
link_dao = evidence_entity_dao_new(database, error);
if (link_dao == NULL ||
!evidence_entity_dao_exists(link_dao, evidence_identifier,
entity_identifier, &exists, error) ||
(!exists && !evidence_entity_dao_link(link_dao, evidence_identifier,
entity_identifier, error))) goto cleanup;
success = TRUE;
cleanup:
evidence_entity_dao_free(link_dao);
g_free(evidence_identifier);
return success;
}
gboolean extraction_drop_service_attach(
Database *database,
const ExtractionDropInfo *info,
const char *entity_identifier,
GError **error)
{
gboolean success = FALSE;
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if (database == NULL || info == NULL || entity_identifier == NULL ||
!g_uuid_string_is_valid(entity_identifier))
{
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_INVALID_ARGUMENT,
"L'entité de destination est invalide.");
return FALSE;
}
if (!database_transaction_begin(database)) goto database_failure;
if (!extraction_drop_service_attach_in_transaction(database, info,
entity_identifier, error))
{
database_transaction_rollback(database);
return FALSE;
}
if (!database_transaction_commit(database)) goto database_failure;
success = TRUE;
return success;
database_failure:
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_DATABASE,
"Impossible d'enregistrer l'association de l'extraction.");
return FALSE;
}
gboolean extraction_drop_service_create_entity(
Database *database,
const ExtractionDropInfo *info,
char **out_entity_identifier,
GError **error)
{
EntityDao *entity_dao = NULL;
EntityRecord *entity = NULL;
char *identifier = NULL;
char *timestamp = NULL;
gboolean success = FALSE;
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if (out_entity_identifier != NULL) *out_entity_identifier = NULL;
if (database == NULL || info == NULL)
{
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_INVALID_ARGUMENT,
"Les paramètres de création d'entité sont invalides.");
return FALSE;
}
identifier = g_uuid_string_random();
timestamp = extraction_drop_service_timestamp();
entity = entity_record_new(identifier, "other", info->file_name,
info->file_name, info->content, 50, timestamp, timestamp,
ENTITY_STATUS_ACTIVE, error);
if (!database_transaction_begin(database))
{
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_DATABASE,
"Impossible de démarrer la création de l'entité.");
goto cleanup;
}
entity_dao = entity_dao_new(database, error);
if (entity == NULL || entity_dao == NULL ||
!entity_dao_insert(entity_dao, entity, error) ||
!extraction_drop_service_attach_in_transaction(database, info,
identifier, error))
{
database_transaction_rollback(database);
goto cleanup;
}
if (!database_transaction_commit(database))
{
extraction_drop_service_set_error(error,
EXTRACTION_DROP_SERVICE_ERROR_DATABASE,
"Impossible de valider la création de l'entité.");
goto cleanup;
}
if (out_entity_identifier != NULL)
*out_entity_identifier = g_strdup(identifier);
success = TRUE;
cleanup:
entity_dao_free(entity_dao);
entity_record_free(entity);
g_free(timestamp);
g_free(identifier);
return success;
}

View file

@ -71,16 +71,6 @@ static const char *const graph_node_position_dao_delete_sql =
static const char *const graph_node_position_dao_delete_all_sql = static const char *const graph_node_position_dao_delete_all_sql =
"DELETE FROM graph_layout_positions;"; "DELETE FROM graph_layout_positions;";
static const char *const graph_node_position_dao_load_viewport_sql =
"SELECT zoom, offset_x, offset_y FROM graph_viewport WHERE id = 1;";
static const char *const graph_node_position_dao_upsert_viewport_sql =
"INSERT INTO graph_viewport(id, zoom, offset_x, offset_y, updated_at) "
"VALUES(1, ?, ?, ?, ?) "
"ON CONFLICT(id) DO UPDATE SET zoom = excluded.zoom, "
"offset_x = excluded.offset_x, offset_y = excluded.offset_y, "
"updated_at = excluded.updated_at;";
/** /**
* @brief Enregistre une erreur littérale. * @brief Enregistre une erreur littérale.
*/ */
@ -879,125 +869,3 @@ cleanup:
return success; return success;
} }
gboolean graph_node_position_dao_load_viewport(
GraphNodePositionDao *position_dao,
double *zoom,
double *offset_x,
double *offset_y,
gboolean *found,
GError **error
)
{
DatabaseStatement *statement = NULL;
DatabaseStatementStepResult step_result;
gboolean success = FALSE;
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if (position_dao == NULL || position_dao->database == NULL ||
zoom == NULL || offset_x == NULL || offset_y == NULL || found == NULL)
{
graph_node_position_dao_set_error_literal(error,
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
"Les paramètres de chargement du cadrage sont invalides.");
return FALSE;
}
*found = FALSE;
statement = database_statement_prepare(position_dao->database,
graph_node_position_dao_load_viewport_sql);
if (statement == NULL)
{
graph_node_position_dao_set_database_error(position_dao, error,
GRAPH_NODE_POSITION_DAO_ERROR_PREPARE,
"Impossible de préparer le chargement du cadrage");
return FALSE;
}
step_result = database_statement_step(statement);
if (step_result == DATABASE_STATEMENT_STEP_ROW)
{
if (!database_statement_column_double(statement, 0, zoom) ||
!database_statement_column_double(statement, 1, offset_x) ||
!database_statement_column_double(statement, 2, offset_y) ||
!isfinite(*zoom) || *zoom <= 0.0 ||
!isfinite(*offset_x) || !isfinite(*offset_y))
{
graph_node_position_dao_set_error_literal(error,
GRAPH_NODE_POSITION_DAO_ERROR_READ,
"Le cadrage enregistré est invalide.");
goto cleanup;
}
*found = TRUE;
}
else if (step_result == DATABASE_STATEMENT_STEP_ERROR)
{
graph_node_position_dao_set_database_error(position_dao, error,
GRAPH_NODE_POSITION_DAO_ERROR_EXECUTE,
"Impossible de charger le cadrage");
goto cleanup;
}
success = TRUE;
cleanup:
database_statement_finalize(statement);
return success;
}
gboolean graph_node_position_dao_upsert_viewport(
GraphNodePositionDao *position_dao,
double zoom,
double offset_x,
double offset_y,
GError **error
)
{
DatabaseStatement *statement = NULL;
char *updated_at = NULL;
gboolean success = FALSE;
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if (position_dao == NULL || position_dao->database == NULL ||
!isfinite(zoom) || zoom <= 0.0 ||
!isfinite(offset_x) || !isfinite(offset_y))
{
graph_node_position_dao_set_error_literal(error,
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
"Le cadrage du graphe est invalide.");
return FALSE;
}
updated_at = graph_node_position_dao_create_utc_timestamp();
statement = database_statement_prepare(position_dao->database,
graph_node_position_dao_upsert_viewport_sql);
if (updated_at == NULL || statement == NULL)
{
graph_node_position_dao_set_database_error(position_dao, error,
GRAPH_NODE_POSITION_DAO_ERROR_PREPARE,
"Impossible de préparer l'enregistrement du cadrage");
goto cleanup;
}
if (!database_statement_bind_double(statement, 1, zoom) ||
!database_statement_bind_double(statement, 2, offset_x) ||
!database_statement_bind_double(statement, 3, offset_y) ||
!database_statement_bind_text(statement, 4, updated_at))
{
graph_node_position_dao_set_database_error(position_dao, error,
GRAPH_NODE_POSITION_DAO_ERROR_BIND,
"Impossible de lier les données du cadrage");
goto cleanup;
}
if (database_statement_step(statement) != DATABASE_STATEMENT_STEP_DONE)
{
graph_node_position_dao_set_database_error(position_dao, error,
GRAPH_NODE_POSITION_DAO_ERROR_EXECUTE,
"Impossible d'enregistrer le cadrage");
goto cleanup;
}
success = TRUE;
cleanup:
database_statement_finalize(statement);
g_free(updated_at);
return success;
}

View file

@ -16,12 +16,12 @@
/** /**
* @brief Version actuelle du schéma SQLite. * @brief Version actuelle du schéma SQLite.
*/ */
#define DATABASE_SCHEMA_VERSION_CURRENT 8 #define DATABASE_SCHEMA_VERSION_CURRENT 7
/** /**
* @brief Version actuelle sous forme textuelle pour metadata. * @brief Version actuelle sous forme textuelle pour metadata.
*/ */
#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "8" #define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "7"
/** /**
* @brief Nom de l'application enregistré dans les métadonnées. * @brief Nom de l'application enregistré dans les métadonnées.
@ -748,40 +748,6 @@ rollback:
return false; return false;
} }
static bool database_migrate_v6_to_v7(Database *database)
{
bool transaction_started = false;
if (database == NULL || !database_transaction_begin(database))
return false;
transaction_started = true;
if (!schema_install_v7(database) ||
!database_update_schema_version(database, "7") ||
!database_transaction_commit(database))
goto rollback;
return true;
rollback:
if (transaction_started && !database_transaction_rollback(database))
g_warning("Impossible dannuler la migration SQLite V6 vers V7.");
return false;
}
static bool database_migrate_v7_to_v8(Database *database)
{
bool transaction_started = false;
if (database == NULL || !database_transaction_begin(database))
return false;
transaction_started = true;
if (!schema_install_v8(database) ||
!database_update_schema_version(database, "8") ||
!database_transaction_commit(database))
goto rollback;
return true;
rollback:
if (transaction_started && !database_transaction_rollback(database))
g_warning("Impossible dannuler la migration SQLite V7 vers V8.");
return false;
}
/** /**
* @brief Garantit atomiquement la présence des extensions du schéma courant. * @brief Garantit atomiquement la présence des extensions du schéma courant.
*/ */
@ -993,17 +959,11 @@ bool database_migrate_to_latest(
break; break;
case 6: case 6:
if (!database_migrate_v6_to_v7(database)) if (!schema_install_v7(database))
return false; return false;
schema_version = 7; schema_version = 7;
break; break;
case 7:
if (!database_migrate_v7_to_v8(database))
return false;
schema_version = 8;
break;
default: default:
database_set_error( database_set_error(
database, database,

View file

@ -233,12 +233,6 @@ bool schema_install_v7(Database *database)
"la migration SQLite V7"); "la migration SQLite V7");
} }
bool schema_install_v8(Database *database)
{
return schema_execute_file(database, "database/schema_v8.sql",
"la migration SQLite V8");
}
bool schema_ensure_current( bool schema_ensure_current(
Database *database Database *database
) )

View file

@ -106,9 +106,6 @@ struct MainWindow
gpointer gpointer
verify_evidence_user_data; verify_evidence_user_data;
MainWindowEvidenceSelectionCallback evidence_selection_callback;
gpointer evidence_selection_user_data;
MainWindowEditEvidenceCallback edit_evidence_callback; MainWindowEditEvidenceCallback edit_evidence_callback;
gpointer edit_evidence_user_data; gpointer edit_evidence_user_data;
MainWindowAnalyzeEmlCallback analyze_eml_callback; MainWindowAnalyzeEmlCallback analyze_eml_callback;
@ -126,9 +123,6 @@ struct MainWindow
gpointer gpointer
graph_node_moved_user_data; graph_node_moved_user_data;
MainWindowExtractionDropCallback extraction_drop_callback;
gpointer extraction_drop_user_data;
MainWindowResetGraphLayoutCallback MainWindowResetGraphLayoutCallback
reset_graph_layout_callback; reset_graph_layout_callback;
@ -514,15 +508,6 @@ static void main_window_on_graph_node_moved(
); );
} }
static void main_window_on_extraction_dropped(const char *file_path,
const char *target_entity_identifier, gpointer user_data)
{
MainWindow *main_window = user_data;
if (main_window != NULL && main_window->extraction_drop_callback != NULL)
main_window->extraction_drop_callback(file_path,
target_entity_identifier, main_window->extraction_drop_user_data);
}
/** /**
* @brief Relaie la demande de vérification provenant du Workspace. * @brief Relaie la demande de vérification provenant du Workspace.
*/ */
@ -634,15 +619,6 @@ static void main_window_on_person_evidence_requested(const char *identifier,
window->person_evidence_callback(identifier, window->person_evidence_callback(identifier,
window->person_evidence_user_data); window->person_evidence_user_data);
} }
static void main_window_on_evidence_activated(const char *identifier,
gpointer user_data)
{
MainWindow *window = user_data;
if (window != NULL && window->evidence_selection_callback != NULL)
window->evidence_selection_callback(identifier,
window->evidence_selection_user_data);
}
/** @brief Relaie la sélection d'une entité vers le contrôleur. */ /** @brief Relaie la sélection d'une entité vers le contrôleur. */
static void main_window_on_entity_selected(const char *identifier, static void main_window_on_entity_selected(const char *identifier,
gpointer user_data) gpointer user_data)
@ -978,8 +954,6 @@ MainWindow *main_window_new(
main_window_on_graph_node_moved, main_window_on_graph_node_moved,
main_window main_window
); );
workspace_set_extraction_drop_callback(main_window->workspace,
main_window_on_extraction_dropped, main_window);
workspace_set_reset_graph_layout_callback( workspace_set_reset_graph_layout_callback(
main_window->workspace, main_window->workspace,
@ -1005,8 +979,6 @@ MainWindow *main_window_new(
main_window_on_person_name_changed, main_window); main_window_on_person_name_changed, main_window);
workspace_set_person_evidence_callback(main_window->workspace, workspace_set_person_evidence_callback(main_window->workspace,
main_window_on_person_evidence_requested, main_window); main_window_on_person_evidence_requested, main_window);
workspace_set_evidence_activated_callback(main_window->workspace,
main_window_on_evidence_activated, main_window);
workspace_set_entity_selected_callback(main_window->workspace, workspace_set_entity_selected_callback(main_window->workspace,
main_window_on_entity_selected, main_window); main_window_on_entity_selected, main_window);
@ -1421,22 +1393,6 @@ void main_window_set_graph(
); );
} }
gboolean main_window_get_graph_view_transform(
MainWindow *main_window, double *zoom, double *offset_x, double *offset_y)
{
return main_window != NULL &&
workspace_get_graph_view_transform(
main_window->workspace, zoom, offset_x, offset_y);
}
void main_window_set_graph_view_transform(
MainWindow *main_window, double zoom, double offset_x, double offset_y)
{
if (main_window != NULL)
workspace_set_graph_view_transform(
main_window->workspace, zoom, offset_x, offset_y);
}
gboolean main_window_select_graph_relation(MainWindow *main_window, gboolean main_window_select_graph_relation(MainWindow *main_window,
const char *relation_identifier) const char *relation_identifier)
{ {
@ -1644,9 +1600,6 @@ void main_window_set_evidence_selection_callback(
return; return;
} }
main_window->evidence_selection_callback = callback;
main_window->evidence_selection_user_data = user_data;
sidebar_set_evidence_selection_callback( sidebar_set_evidence_selection_callback(
main_window->sidebar, main_window->sidebar,
callback, callback,
@ -1803,16 +1756,6 @@ void main_window_set_graph_node_moved_callback(
user_data; user_data;
} }
void main_window_set_extraction_drop_callback(
MainWindow *main_window,
MainWindowExtractionDropCallback callback,
gpointer user_data)
{
if (main_window == NULL) return;
main_window->extraction_drop_callback = callback;
main_window->extraction_drop_user_data = user_data;
}
void main_window_set_reset_graph_layout_callback( void main_window_set_reset_graph_layout_callback(
MainWindow *main_window, MainWindow *main_window,
MainWindowResetGraphLayoutCallback callback, MainWindowResetGraphLayoutCallback callback,

View file

@ -39,8 +39,6 @@ struct EntityDetailsPanel
GtkWidget *person_name_button; GtkWidget *person_name_button;
GtkWidget *person_evidence_button; GtkWidget *person_evidence_button;
GtkWidget *person_evidence_summary_label; GtkWidget *person_evidence_summary_label;
GtkWidget *entity_evidence_box;
GtkWidget *evidence_buttons_box;
char *selected_entity_identifier; char *selected_entity_identifier;
char *selected_entity_type; char *selected_entity_type;
@ -63,8 +61,6 @@ struct EntityDetailsPanel
gpointer person_name_user_data; gpointer person_name_user_data;
EntityDetailsPanelPersonEvidenceCallback person_evidence_callback; EntityDetailsPanelPersonEvidenceCallback person_evidence_callback;
gpointer person_evidence_user_data; gpointer person_evidence_user_data;
EntityDetailsPanelEvidenceActivatedCallback evidence_activated_callback;
gpointer evidence_activated_user_data;
EntityDetailsPanelOsintCallback osint_callback; EntityDetailsPanelOsintCallback osint_callback;
gpointer osint_user_data; gpointer osint_user_data;
}; };
@ -102,18 +98,6 @@ static void entity_details_panel_on_person_evidence_clicked(GtkButton *button,
panel->person_evidence_user_data); panel->person_evidence_user_data);
} }
static void entity_details_panel_on_evidence_activated(GtkButton *button,
gpointer user_data)
{
EntityDetailsPanel *panel = user_data;
const char *identifier = g_object_get_data(G_OBJECT(button),
"evidence-identifier");
if (panel != NULL && panel->evidence_activated_callback != NULL &&
identifier != NULL)
panel->evidence_activated_callback(identifier,
panel->evidence_activated_user_data);
}
/** @brief Relaie le nouveau nom affiché explicitement enregistré. */ /** @brief Relaie le nouveau nom affiché explicitement enregistré. */
static void entity_details_panel_on_person_name_clicked(GtkButton *button, static void entity_details_panel_on_person_name_clicked(GtkButton *button,
gpointer user_data) gpointer user_data)
@ -862,30 +846,23 @@ EntityDetailsPanel *entity_details_panel_new(void)
GTK_WIDGET(details_panel->person_name_entry)); GTK_WIDGET(details_panel->person_name_entry));
gtk_box_append(GTK_BOX(details_panel->person_role_box), gtk_box_append(GTK_BOX(details_panel->person_role_box),
details_panel->person_name_button); details_panel->person_name_button);
details_panel->entity_evidence_box = gtk_box_new(
GTK_ORIENTATION_VERTICAL, 6);
details_panel->person_evidence_button = gtk_button_new_with_label( details_panel->person_evidence_button = gtk_button_new_with_label(
"Gérer les pièces jointes"); "Gérer les pièces jointes");
details_panel->person_evidence_summary_label = gtk_label_new( details_panel->person_evidence_summary_label = gtk_label_new(
"Pièces jointes : chargement…"); "Pièces jointes : chargement…");
details_panel->evidence_buttons_box = gtk_box_new(
GTK_ORIENTATION_VERTICAL, 4);
gtk_label_set_xalign(GTK_LABEL( gtk_label_set_xalign(GTK_LABEL(
details_panel->person_evidence_summary_label), 0.0F); details_panel->person_evidence_summary_label), 0.0F);
gtk_label_set_wrap(GTK_LABEL( gtk_label_set_wrap(GTK_LABEL(
details_panel->person_evidence_summary_label), TRUE); details_panel->person_evidence_summary_label), TRUE);
gtk_label_set_selectable(GTK_LABEL( gtk_label_set_selectable(GTK_LABEL(
details_panel->person_evidence_summary_label), TRUE); details_panel->person_evidence_summary_label), TRUE);
gtk_box_append(GTK_BOX(details_panel->entity_evidence_box), gtk_box_append(GTK_BOX(details_panel->person_role_box),
gtk_label_new("Preuves associées à cette entité")); gtk_label_new("Preuves associées à cette personne"));
gtk_box_append(GTK_BOX(details_panel->entity_evidence_box), gtk_box_append(GTK_BOX(details_panel->person_role_box),
details_panel->person_evidence_summary_label); details_panel->person_evidence_summary_label);
gtk_box_append(GTK_BOX(details_panel->entity_evidence_box), gtk_box_append(GTK_BOX(details_panel->person_role_box),
details_panel->evidence_buttons_box);
gtk_box_append(GTK_BOX(details_panel->entity_evidence_box),
details_panel->person_evidence_button); details_panel->person_evidence_button);
gtk_box_append(GTK_BOX(details_box), details_panel->person_role_box); gtk_box_append(GTK_BOX(details_box), details_panel->person_role_box);
gtk_box_append(GTK_BOX(details_box), details_panel->entity_evidence_box);
g_signal_connect(details_panel->person_role_dropdown, "notify::selected", g_signal_connect(details_panel->person_role_dropdown, "notify::selected",
G_CALLBACK(entity_details_panel_on_person_role_changed), details_panel); G_CALLBACK(entity_details_panel_on_person_role_changed), details_panel);
g_signal_connect(details_panel->person_confidence_button, "clicked", g_signal_connect(details_panel->person_confidence_button, "clicked",
@ -1318,15 +1295,6 @@ void entity_details_panel_set_person_evidence_callback(
details_panel->person_evidence_callback = callback; details_panel->person_evidence_callback = callback;
details_panel->person_evidence_user_data = user_data; details_panel->person_evidence_user_data = user_data;
} }
void entity_details_panel_set_evidence_activated_callback(
EntityDetailsPanel *details_panel,
EntityDetailsPanelEvidenceActivatedCallback callback,
gpointer user_data)
{
if (details_panel == NULL) return;
details_panel->evidence_activated_callback = callback;
details_panel->evidence_activated_user_data = user_data;
}
void entity_details_panel_set_osint_callback(EntityDetailsPanel *details_panel, void entity_details_panel_set_osint_callback(EntityDetailsPanel *details_panel,
EntityDetailsPanelOsintCallback callback, gpointer user_data) EntityDetailsPanelOsintCallback callback, gpointer user_data)
{ {
@ -1342,52 +1310,24 @@ void entity_details_panel_set_osint_callback(EntityDetailsPanel *details_panel,
void entity_details_panel_set_person_evidences(EntityDetailsPanel *panel, void entity_details_panel_set_person_evidences(EntityDetailsPanel *panel,
const GPtrArray *records) const GPtrArray *records)
{ {
GtkWidget *child = NULL; GString *text = NULL;
if (panel == NULL || panel->person_evidence_summary_label == NULL) return; if (panel == NULL || panel->person_evidence_summary_label == NULL) return;
while (panel->evidence_buttons_box != NULL && text = g_string_new(NULL);
(child = gtk_widget_get_first_child(
panel->evidence_buttons_box)) != NULL)
gtk_box_remove(GTK_BOX(panel->evidence_buttons_box), child);
if (records == NULL || records->len == 0) if (records == NULL || records->len == 0)
gtk_label_set_text(GTK_LABEL(panel->person_evidence_summary_label), g_string_append(text, "Aucune pièce jointe");
"Aucune pièce jointe");
else else
{
char *summary = g_strdup_printf("%u pièce%s jointe%s",
records->len, records->len > 1U ? "s" : "",
records->len > 1U ? "s" : "");
gtk_label_set_text(GTK_LABEL(panel->person_evidence_summary_label),
summary);
g_free(summary);
for (guint index = 0; index < records->len; index++) for (guint index = 0; index < records->len; index++)
{ {
const EvidenceRecord *record = g_ptr_array_index(records, index); const EvidenceRecord *record = g_ptr_array_index(records, index);
GtkWidget *button = NULL;
char *label = NULL;
if (record == NULL) continue; if (record == NULL) continue;
label = g_strdup_printf("%s — %s", if (text->len > 0) g_string_append_c(text, '\n');
g_string_append_printf(text, "• %s — %s",
evidence_record_get_original_name(record), evidence_record_get_original_name(record),
evidence_record_get_type_identifier(record)); evidence_record_get_type_identifier(record));
button = gtk_button_new_with_label(label);
gtk_widget_set_halign(button, GTK_ALIGN_FILL);
gtk_widget_set_tooltip_text(button, label);
gtk_widget_add_css_class(button, "flat");
if (GTK_IS_LABEL(gtk_button_get_child(GTK_BUTTON(button))))
{
GtkLabel *button_label = GTK_LABEL(
gtk_button_get_child(GTK_BUTTON(button)));
gtk_label_set_xalign(button_label, 0.0F);
gtk_label_set_ellipsize(button_label, PANGO_ELLIPSIZE_END);
gtk_label_set_max_width_chars(button_label, 28);
}
g_object_set_data_full(G_OBJECT(button), "evidence-identifier",
g_strdup(evidence_record_get_identifier(record)), g_free);
g_signal_connect(button, "clicked",
G_CALLBACK(entity_details_panel_on_evidence_activated), panel);
gtk_box_append(GTK_BOX(panel->evidence_buttons_box), button);
g_free(label);
}
} }
gtk_label_set_text(GTK_LABEL(panel->person_evidence_summary_label),
text->str);
g_string_free(text, TRUE);
} }
gboolean entity_details_panel_is_open( gboolean entity_details_panel_is_open(

View file

@ -12,7 +12,6 @@
#include "models/social_platform.h" #include "models/social_platform.h"
#include <glib.h> #include <glib.h>
#include <math.h>
#include <pango/pangocairo.h> #include <pango/pangocairo.h>
#define INVESTIGATION_GRAPH_VIEW_MARGIN 32.0 #define INVESTIGATION_GRAPH_VIEW_MARGIN 32.0
@ -121,8 +120,6 @@ struct InvestigationGraphView
InvestigationGraphViewNodeMovedCallback node_moved_callback; InvestigationGraphViewNodeMovedCallback node_moved_callback;
gpointer node_moved_user_data; gpointer node_moved_user_data;
InvestigationGraphViewExtractionDropCallback extraction_drop_callback;
gpointer extraction_drop_user_data;
double node_drag_pointer_offset_x; double node_drag_pointer_offset_x;
double node_drag_pointer_offset_y; double node_drag_pointer_offset_y;
@ -131,46 +128,8 @@ struct InvestigationGraphView
gboolean dragging; gboolean dragging;
gboolean node_dragging; gboolean node_dragging;
gboolean click_suppressed; gboolean click_suppressed;
GtkDropTarget *extraction_drop_target;
}; };
static gboolean investigation_graph_view_screen_to_logical(
const InvestigationGraphView *graph_view, double screen_x, double screen_y,
double *logical_x, double *logical_y);
static InvestigationGraphNodeLayout *investigation_graph_view_find_node_at(
InvestigationGraphView *graph_view, double logical_x, double logical_y);
/** @brief Relaie un chemin d'extraction et l'éventuelle entité visée. */
static gboolean investigation_graph_view_on_extraction_drop(
GtkDropTarget *target, const GValue *value, double x, double y,
gpointer user_data)
{
InvestigationGraphView *graph_view = user_data;
InvestigationGraphNodeLayout *node_layout = NULL;
const char *path = NULL;
const char *entity_identifier = NULL;
double logical_x = 0.0;
double logical_y = 0.0;
(void) target;
if (graph_view == NULL || value == NULL ||
!G_VALUE_HOLDS_STRING(value)) return FALSE;
path = g_value_get_string(value);
if (path == NULL || path[0] == '\0') return FALSE;
if (investigation_graph_view_screen_to_logical(graph_view, x, y,
&logical_x, &logical_y))
{
node_layout = investigation_graph_view_find_node_at(graph_view,
logical_x, logical_y);
if (node_layout != NULL && node_layout->entity_record != NULL)
entity_identifier = entity_record_get_identifier(
node_layout->entity_record);
}
if (graph_view->extraction_drop_callback == NULL) return FALSE;
graph_view->extraction_drop_callback(path, entity_identifier,
graph_view->extraction_drop_user_data);
return TRUE;
}
static const char *investigation_graph_view_get_node_identifier( static const char *investigation_graph_view_get_node_identifier(
const InvestigationGraphNodeLayout *node_layout const InvestigationGraphNodeLayout *node_layout
); );
@ -3989,13 +3948,6 @@ InvestigationGraphView *investigation_graph_view_new(void)
TRUE TRUE
); );
graph_view->extraction_drop_target = GTK_DROP_TARGET(
gtk_drop_target_new(G_TYPE_STRING, GDK_ACTION_COPY));
g_signal_connect(graph_view->extraction_drop_target, "drop",
G_CALLBACK(investigation_graph_view_on_extraction_drop), graph_view);
gtk_widget_add_controller(graph_view->drawing_area,
GTK_EVENT_CONTROLLER(graph_view->extraction_drop_target));
gtk_drawing_area_set_content_width( gtk_drawing_area_set_content_width(
GTK_DRAWING_AREA( GTK_DRAWING_AREA(
graph_view->drawing_area graph_view->drawing_area
@ -4462,16 +4414,6 @@ void investigation_graph_view_set_node_moved_callback(
user_data; user_data;
} }
void investigation_graph_view_set_extraction_drop_callback(
InvestigationGraphView *graph_view,
InvestigationGraphViewExtractionDropCallback callback,
gpointer user_data)
{
if (graph_view == NULL) return;
graph_view->extraction_drop_callback = callback;
graph_view->extraction_drop_user_data = user_data;
}
const EntityRecord *investigation_graph_view_get_selected_entity( const EntityRecord *investigation_graph_view_get_selected_entity(
const InvestigationGraphView *graph_view const InvestigationGraphView *graph_view
) )
@ -4643,39 +4585,6 @@ void investigation_graph_view_reset_view(
} }
} }
gboolean investigation_graph_view_get_view_transform(
const InvestigationGraphView *graph_view,
double *zoom,
double *offset_x,
double *offset_y)
{
if (graph_view == NULL || graph_view->graph_model == NULL ||
zoom == NULL || offset_x == NULL || offset_y == NULL)
return FALSE;
*zoom = graph_view->zoom;
*offset_x = graph_view->offset_x;
*offset_y = graph_view->offset_y;
return TRUE;
}
void investigation_graph_view_set_view_transform(
InvestigationGraphView *graph_view,
double zoom,
double offset_x,
double offset_y)
{
if (graph_view == NULL ||
zoom < INVESTIGATION_GRAPH_VIEW_MIN_ZOOM ||
zoom > INVESTIGATION_GRAPH_VIEW_MAX_ZOOM ||
!isfinite(zoom) || !isfinite(offset_x) || !isfinite(offset_y))
return;
graph_view->zoom = zoom;
graph_view->offset_x = offset_x;
graph_view->offset_y = offset_y;
if (graph_view->drawing_area != NULL)
gtk_widget_queue_draw(graph_view->drawing_area);
}
void investigation_graph_view_reset_layout( void investigation_graph_view_reset_layout(
InvestigationGraphView *graph_view InvestigationGraphView *graph_view
) )

View file

@ -13,38 +13,6 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <string.h> #include <string.h>
/** @brief Prépare le chemin du fichier pour un glisser-déposer. */
static GdkContentProvider *investigation_tree_view_on_drag_prepare(
GtkDragSource *source, double x, double y, gpointer user_data)
{
const char *path = g_object_get_data(G_OBJECT(source),
"investigation-node-path");
(void) x; (void) y; (void) user_data;
if (path == NULL || path[0] == '\0') return NULL;
return gdk_content_provider_new_typed(G_TYPE_STRING, path);
}
/** @brief Vérifie qu'un nœud est un fichier `.txt` sous `Extractions`. */
static gboolean investigation_tree_view_node_is_extraction(
const InvestigationNode *node)
{
const InvestigationNode *ancestor = NULL;
const char *name = NULL;
if (node == NULL ||
investigation_node_get_type(node) != INVESTIGATION_NODE_FILE)
return FALSE;
name = investigation_node_get_name(node);
if (name == NULL || !g_str_has_suffix(name, ".txt")) return FALSE;
ancestor = investigation_node_get_parent(node);
while (ancestor != NULL)
{
if (g_strcmp0(investigation_node_get_name(ancestor),
"Extractions") == 0) return TRUE;
ancestor = investigation_node_get_parent(ancestor);
}
return FALSE;
}
/* /*
* InvestigationTreeItem * InvestigationTreeItem
* --------------------- * ---------------------
@ -280,7 +248,6 @@ static void investigation_tree_view_factory_setup(
GtkWidget *row_box = NULL; GtkWidget *row_box = NULL;
GtkWidget *image = NULL; GtkWidget *image = NULL;
GtkWidget *label = NULL; GtkWidget *label = NULL;
GtkDragSource *drag_source = NULL;
(void)factory; (void)factory;
(void)user_data; (void)user_data;
@ -325,14 +292,6 @@ static void investigation_tree_view_factory_setup(
row_box row_box
); );
drag_source = gtk_drag_source_new();
gtk_drag_source_set_actions(drag_source, GDK_ACTION_COPY);
g_signal_connect(drag_source, "prepare",
G_CALLBACK(investigation_tree_view_on_drag_prepare), NULL);
g_object_set_data(G_OBJECT(row_box), "investigation-drag-source",
drag_source);
gtk_widget_add_controller(row_box, GTK_EVENT_CONTROLLER(drag_source));
gtk_list_item_set_child( gtk_list_item_set_child(
list_item, list_item,
expander expander
@ -447,7 +406,6 @@ static void investigation_tree_view_factory_bind(
GtkWidget *label = NULL; GtkWidget *label = NULL;
const char *node_name = NULL; const char *node_name = NULL;
GtkDragSource *drag_source = NULL;
(void)factory; (void)factory;
(void)user_data; (void)user_data;
@ -497,13 +455,6 @@ static void investigation_tree_view_factory_bind(
return; return;
} }
drag_source = GTK_DRAG_SOURCE(g_object_get_data(G_OBJECT(row_box),
"investigation-drag-source"));
if (drag_source != NULL)
g_object_set_data_full(G_OBJECT(drag_source), "investigation-node-path",
investigation_tree_view_node_is_extraction(node)
? g_strdup(investigation_node_get_path(node)) : NULL, g_free);
image = gtk_widget_get_first_child( image = gtk_widget_get_first_child(
row_box row_box
); );

View file

@ -129,9 +129,6 @@ struct Workspace
gpointer gpointer
graph_node_moved_user_data; graph_node_moved_user_data;
WorkspaceExtractionDropCallback extraction_drop_callback;
gpointer extraction_drop_user_data;
WorkspaceResetGraphLayoutCallback WorkspaceResetGraphLayoutCallback
reset_graph_layout_callback; reset_graph_layout_callback;
@ -158,8 +155,6 @@ struct Workspace
gpointer person_name_user_data; gpointer person_name_user_data;
WorkspacePersonEvidenceCallback person_evidence_callback; WorkspacePersonEvidenceCallback person_evidence_callback;
gpointer person_evidence_user_data; gpointer person_evidence_user_data;
WorkspaceEvidenceActivatedCallback evidence_activated_callback;
gpointer evidence_activated_user_data;
WorkspaceEntitySelectedCallback entity_selected_callback; WorkspaceEntitySelectedCallback entity_selected_callback;
gpointer entity_selected_user_data; gpointer entity_selected_user_data;
@ -182,10 +177,6 @@ struct Workspace
OsintActionCatalog *osint_action_catalog; OsintActionCatalog *osint_action_catalog;
WorkspaceGraphState graph_state; WorkspaceGraphState graph_state;
gboolean saved_graph_transform_valid;
double saved_graph_zoom;
double saved_graph_offset_x;
double saved_graph_offset_y;
}; };
/** /**
@ -969,24 +960,6 @@ static void workspace_on_person_evidence_requested(const char *identifier,
workspace->person_evidence_user_data); workspace->person_evidence_user_data);
} }
static void workspace_on_evidence_activated(const char *identifier,
gpointer user_data)
{
Workspace *workspace = user_data;
if (workspace != NULL && workspace->evidence_activated_callback != NULL)
workspace->evidence_activated_callback(identifier,
workspace->evidence_activated_user_data);
}
static void workspace_on_extraction_dropped(const char *file_path,
const char *target_entity_identifier, gpointer user_data)
{
Workspace *workspace = user_data;
if (workspace != NULL && workspace->extraction_drop_callback != NULL)
workspace->extraction_drop_callback(file_path, target_entity_identifier,
workspace->extraction_drop_user_data);
}
/** /**
* @brief Désélectionne le nœud lorsque le volet est fermé manuellement. * @brief Désélectionne le nœud lorsque le volet est fermé manuellement.
*/ */
@ -1966,8 +1939,6 @@ Workspace *workspace_new(void)
workspace_on_graph_node_moved, workspace_on_graph_node_moved,
workspace workspace
); );
investigation_graph_view_set_extraction_drop_callback(
workspace->graph_view, workspace_on_extraction_dropped, workspace);
entity_details_panel_set_close_callback( entity_details_panel_set_close_callback(
workspace->entity_details_panel, workspace->entity_details_panel,
@ -1993,9 +1964,6 @@ Workspace *workspace_new(void)
entity_details_panel_set_person_evidence_callback( entity_details_panel_set_person_evidence_callback(
workspace->entity_details_panel, workspace->entity_details_panel,
workspace_on_person_evidence_requested, workspace); workspace_on_person_evidence_requested, workspace);
entity_details_panel_set_evidence_activated_callback(
workspace->entity_details_panel, workspace_on_evidence_activated,
workspace);
entity_details_panel_set_osint_callback(workspace->entity_details_panel, entity_details_panel_set_osint_callback(workspace->entity_details_panel,
workspace_on_entity_osint_requested, workspace); workspace_on_entity_osint_requested, workspace);
@ -3069,13 +3037,6 @@ void workspace_set_graph_loading(
workspace->entity_details_panel workspace->entity_details_panel
); );
workspace->saved_graph_transform_valid =
investigation_graph_view_get_view_transform(
workspace->graph_view,
&workspace->saved_graph_zoom,
&workspace->saved_graph_offset_x,
&workspace->saved_graph_offset_y);
investigation_graph_view_clear( investigation_graph_view_clear(
workspace->graph_view workspace->graph_view
); );
@ -3181,16 +3142,6 @@ void workspace_set_graph(
graph_model graph_model
); );
if (workspace->saved_graph_transform_valid)
{
investigation_graph_view_set_view_transform(
workspace->graph_view,
workspace->saved_graph_zoom,
workspace->saved_graph_offset_x,
workspace->saved_graph_offset_y);
workspace->saved_graph_transform_valid = FALSE;
}
workspace->graph_state = workspace->graph_state =
WORKSPACE_GRAPH_STATE_READY; WORKSPACE_GRAPH_STATE_READY;
@ -3229,24 +3180,6 @@ void workspace_set_graph(
); );
} }
gboolean workspace_get_graph_view_transform(
Workspace *workspace, double *zoom, double *offset_x, double *offset_y)
{
if (workspace == NULL || workspace->graph_state != WORKSPACE_GRAPH_STATE_READY)
return FALSE;
return investigation_graph_view_get_view_transform(
workspace->graph_view, zoom, offset_x, offset_y);
}
void workspace_set_graph_view_transform(
Workspace *workspace, double zoom, double offset_x, double offset_y)
{
if (workspace == NULL || workspace->graph_state != WORKSPACE_GRAPH_STATE_READY)
return;
investigation_graph_view_set_view_transform(
workspace->graph_view, zoom, offset_x, offset_y);
}
void workspace_set_graph_error( void workspace_set_graph_error(
Workspace *workspace, Workspace *workspace,
const char *message const char *message
@ -3473,16 +3406,6 @@ void workspace_set_graph_node_moved_callback(
user_data; user_data;
} }
void workspace_set_extraction_drop_callback(
Workspace *workspace,
WorkspaceExtractionDropCallback callback,
gpointer user_data)
{
if (workspace == NULL) return;
workspace->extraction_drop_callback = callback;
workspace->extraction_drop_user_data = user_data;
}
void workspace_set_reset_graph_layout_callback( void workspace_set_reset_graph_layout_callback(
Workspace *workspace, Workspace *workspace,
WorkspaceResetGraphLayoutCallback callback, WorkspaceResetGraphLayoutCallback callback,
@ -3600,15 +3523,6 @@ void workspace_set_person_evidence_callback(Workspace *workspace,
workspace->person_evidence_callback = callback; workspace->person_evidence_callback = callback;
workspace->person_evidence_user_data = user_data; workspace->person_evidence_user_data = user_data;
} }
void workspace_set_evidence_activated_callback(
Workspace *workspace,
WorkspaceEvidenceActivatedCallback callback,
gpointer user_data)
{
if (workspace == NULL) return;
workspace->evidence_activated_callback = callback;
workspace->evidence_activated_user_data = user_data;
}
void workspace_set_entity_selected_callback(Workspace *workspace, void workspace_set_entity_selected_callback(Workspace *workspace,
WorkspaceEntitySelectedCallback callback, gpointer user_data) WorkspaceEntitySelectedCallback callback, gpointer user_data)
{ {

View file

@ -525,8 +525,7 @@ static void test_database_initialize_valid_database(void)
"FROM investigation;" "FROM investigation;"
); );
assert(strcmp(schema_version, "8") == 0); assert(strcmp(schema_version, "7") == 0);
test_database_assert_table_exists(database, "graph_viewport");
test_database_assert_table_exists(database, "osint_executions"); test_database_assert_table_exists(database, "osint_executions");
test_database_assert_table_exists(database, "osint_execution_entities"); test_database_assert_table_exists(database, "osint_execution_entities");
test_database_assert_table_exists(database, "osint_execution_relations"); test_database_assert_table_exists(database, "osint_execution_relations");
@ -990,7 +989,7 @@ static void test_database_migrate_v1_to_v2(void)
assert( assert(
strcmp( strcmp(
schema_version, schema_version,
"8" "6"
) == 0 ) == 0
); );

View file

@ -1,183 +0,0 @@
#include "core/extraction_drop_service.h"
#include "dao/entity_dao.h"
#include "dao/evidence_dao.h"
#include "dao/evidence_entity_dao.h"
#include "database/database.h"
#include "models/entity_record.h"
#include <glib.h>
#include <glib/gstdio.h>
typedef struct
{
char *root;
char *extractions;
char *file_path;
char *database_path;
Database *database;
} ExtractionDropFixture;
static void fixture_setup(ExtractionDropFixture *fixture, gconstpointer data)
{
GError *error = NULL;
(void) data;
fixture->root = g_dir_make_tmp("labfy-extraction-drop-XXXXXX", &error);
g_assert_no_error(error);
fixture->extractions = g_build_filename(fixture->root,
"02_Preuves_Traitees", "Extractions", "OSINT", NULL);
g_assert_cmpint(g_mkdir_with_parents(fixture->extractions, 0700), ==, 0);
fixture->file_path = g_build_filename(fixture->extractions,
"resultat.txt", NULL);
g_assert_true(g_file_set_contents(fixture->file_path,
"Cible : exemple.test\nAction : test\n\nRésultat public", -1, &error));
g_assert_no_error(error);
fixture->database_path = g_build_filename(fixture->root,
"Enquete.sqlite", NULL);
g_assert_true(database_initialize(fixture->database_path,
"Extraction", fixture->root));
fixture->database = database_open(fixture->database_path);
g_assert_nonnull(fixture->database);
g_assert_true(database_migrate_to_latest(fixture->database));
}
static void fixture_teardown(ExtractionDropFixture *fixture,
gconstpointer data)
{
(void) data;
database_close(fixture->database);
g_remove(fixture->file_path);
g_remove(fixture->database_path);
g_rmdir(fixture->extractions);
{
char *directory = g_path_get_dirname(fixture->extractions);
g_rmdir(directory);
g_free(directory);
}
{
char *directory = g_build_filename(fixture->root,
"02_Preuves_Traitees", NULL);
g_rmdir(directory);
g_free(directory);
}
g_rmdir(fixture->root);
g_free(fixture->database_path);
g_free(fixture->file_path);
g_free(fixture->extractions);
g_free(fixture->root);
}
static char *insert_entity(Database *database)
{
EntityDao *dao = NULL;
EntityRecord *record = NULL;
char *identifier = g_uuid_string_random();
GError *error = NULL;
dao = entity_dao_new(database, &error);
record = entity_record_new(identifier, "other", "Destination",
"Destination", NULL, 50, "2026-07-23T12:00:00Z",
"2026-07-23T12:00:00Z", ENTITY_STATUS_ACTIVE, &error);
g_assert_no_error(error);
g_assert_true(entity_dao_insert(dao, record, &error));
g_assert_no_error(error);
entity_record_free(record);
entity_dao_free(dao);
return identifier;
}
static void test_prepare_rejects_outside(ExtractionDropFixture *fixture,
gconstpointer data)
{
ExtractionDropInfo *info = NULL;
char *outside = g_build_filename(fixture->root, "hors.txt", NULL);
GError *error = NULL;
(void) data;
g_assert_true(g_file_set_contents(outside, "hors enquête", -1, &error));
g_assert_no_error(error);
info = extraction_drop_service_prepare(fixture->root, outside, &error);
g_assert_null(info);
g_assert_error(error, EXTRACTION_DROP_SERVICE_ERROR,
EXTRACTION_DROP_SERVICE_ERROR_OUTSIDE_INVESTIGATION);
g_clear_error(&error);
g_remove(outside);
g_free(outside);
}
static void test_attach_persists_and_is_idempotent(
ExtractionDropFixture *fixture, gconstpointer data)
{
ExtractionDropInfo *info = NULL;
EvidenceDao *evidence_dao = NULL;
EvidenceEntityDao *link_dao = NULL;
GPtrArray *records = NULL;
char *entity_identifier = NULL;
gboolean exists = FALSE;
GError *error = NULL;
(void) data;
info = extraction_drop_service_prepare(fixture->root,
fixture->file_path, &error);
g_assert_no_error(error);
g_assert_nonnull(info);
g_assert_cmpstr(extraction_drop_info_get_source(info), ==,
"Cible : exemple.test");
entity_identifier = insert_entity(fixture->database);
g_assert_true(extraction_drop_service_attach(fixture->database, info,
entity_identifier, &error));
g_assert_no_error(error);
g_assert_true(extraction_drop_service_attach(fixture->database, info,
entity_identifier, &error));
g_assert_no_error(error);
evidence_dao = evidence_dao_new(fixture->database, &error);
records = evidence_dao_list_all(evidence_dao, &error);
g_assert_no_error(error);
g_assert_cmpuint(records->len, ==, 1U);
link_dao = evidence_entity_dao_new(fixture->database, &error);
g_assert_true(evidence_entity_dao_exists(link_dao,
evidence_record_get_identifier(g_ptr_array_index(records, 0U)),
entity_identifier, &exists, &error));
g_assert_no_error(error);
g_assert_true(exists);
evidence_entity_dao_free(link_dao);
g_ptr_array_unref(records);
evidence_dao_free(evidence_dao);
g_free(entity_identifier);
extraction_drop_info_free(info);
}
static void test_create_entity_persists(ExtractionDropFixture *fixture,
gconstpointer data)
{
ExtractionDropInfo *info = NULL;
EntityDao *entity_dao = NULL;
EntityRecord *entity = NULL;
char *identifier = NULL;
GError *error = NULL;
(void) data;
info = extraction_drop_service_prepare(fixture->root,
fixture->file_path, &error);
g_assert_true(extraction_drop_service_create_entity(fixture->database,
info, &identifier, &error));
g_assert_no_error(error);
g_assert_true(g_uuid_string_is_valid(identifier));
entity_dao = entity_dao_new(fixture->database, &error);
entity = entity_dao_find_by_identifier(entity_dao, identifier, &error);
g_assert_no_error(error);
g_assert_nonnull(entity);
g_assert_cmpstr(entity_record_get_type_identifier(entity), ==, "other");
entity_record_free(entity);
entity_dao_free(entity_dao);
g_free(identifier);
extraction_drop_info_free(info);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add("/extraction-drop/outside", ExtractionDropFixture, NULL,
fixture_setup, test_prepare_rejects_outside, fixture_teardown);
g_test_add("/extraction-drop/attach", ExtractionDropFixture, NULL,
fixture_setup, test_attach_persists_and_is_idempotent, fixture_teardown);
g_test_add("/extraction-drop/create", ExtractionDropFixture, NULL,
fixture_setup, test_create_entity_persists, fixture_teardown);
return g_test_run();
}