labfy-investigation/src/core/application.c
2026-07-22 08:59:57 +02:00

5100 lines
109 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/******************************************************************************
* @file application.c
* @brief Implémentation du cycle de vie de l'application GTK.
******************************************************************************/
#include "core/application.h"
#include "core/investigation_node.h"
#include "core/investigation_project.h"
#include "core/investigation_session.h"
#include "core/investigation_tree_model.h"
#include "core/investigation_graph_load_task.h"
#include "models/investigation_graph_layout.h"
#include "models/investigation_graph_model.h"
#include "models/investigation_record.h"
#include "core/investigation_tree_builder.h"
#include "views/create_investigation_dialog.h"
#include "views/folder_dialog.h"
#include "views/main_window.h"
#include "views/application_message_dialog.h"
#include "core/task_manager.h"
#include "core/background_task.h"
#include "core/tool_initializer.h"
#include "views/file_dialog.h"
#include "core/evidence_import_task.h"
#include "models/evidence_record.h"
#include "dao/evidence_type_dao.h"
#include "dao/graph_node_position_dao.h"
#include "views/evidence_import_dialog.h"
#include "views/create_relation_dialog.h"
#include "dao/evidence_dao.h"
#include "dao/entity_dao.h"
#include "models/evidence_type.h"
#include "widgets/evidence_list_model.h"
#include "widgets/evidence_category_model.h"
#include "core/evidence_integrity_task.h"
#include "core/evidence_integrity_verifier.h"
#include "database/database.h"
#include <gtk/gtk.h>
#include <errno.h>
/**
* @brief Identifiant unique utilisé par GLib pour l'application.
*/
#define APPLICATION_ID "com.labfytools.investigation"
/**
* @brief Erreurs produites lors du chargement d'une enquête.
*/
typedef enum
{
APPLICATION_OPEN_ERROR_INVALID_ARGUMENT,
APPLICATION_OPEN_ERROR_INVALID_PROJECT,
APPLICATION_OPEN_ERROR_TREE_BUILD,
APPLICATION_OPEN_ERROR_INSTALL,
APPLICATION_OPEN_ERROR_EVIDENCE
} ApplicationOpenError;
/**
* @brief Domaine d'erreur du chargement d'une enquête.
*/
#define APPLICATION_OPEN_ERROR \
application_open_error_quark()
/**
* @brief Dossier racine contenant les preuves originales.
*/
#define APPLICATION_EVIDENCE_ROOT_DIRECTORY \
"01_Preuves_Originales"
/**
* @brief Retourne le domaine d'erreur du chargement d'une enquête.
*
* @return Quark GLib du domaine d'erreur.
*/
static GQuark application_open_error_quark(void)
{
return g_quark_from_static_string(
"labfy-investigation-application-open-error"
);
}
/**
* @brief État interne de l'application.
*
* Cette structure reste privée au module afin d'empêcher les autres
* composants de manipuler directement les objets GTK.
*/
struct Application
{
GtkApplication *gtk_application;
MainWindow *main_window;
InvestigationSession *session;
InvestigationTreeModel *tree_model;
TaskManager *task_manager;
ToolInitializer *tool_initializer;
EvidenceListModel *evidence_list_model;
EvidenceCategoryModel *evidence_category_model;
InvestigationGraphLoadTask *graph_load_task;
InvestigationGraphModel *graph_model;
InvestigationGraphLayout *graph_layout;
guint64 graph_load_generation;
char *selected_evidence_identifier;
};
/**
* @brief Contexte ApplicationOpenErrorconservé jusquà la fin dun import.
*/
typedef struct
{
Application *application;
char *investigation_root_path;
} ApplicationEvidenceImportContext;
/**
* @brief Contexte conservé jusqu'à la fin d'un chargement de graphe.
*/
typedef struct
{
Application *application;
guint64 generation;
} ApplicationGraphLoadContext;
/**
* @brief Contexte conservé pendant le dialogue de métadonnées.
*
* Le chemin fourni par FileDialog n'est valide que pendant son callback.
* Il doit donc être copié avant l'ouverture du second dialogue.
*/
typedef struct
{
Application *application;
char *file_path;
} ApplicationEvidenceDialogContext;
/**
* @brief Contexte conservé jusqu'à la fin d'une vérification.
*/
typedef struct
{
Application *application;
char *investigation_root_path;
char *database_path;
char *evidence_identifier;
char *original_name;
} ApplicationEvidenceIntegrityContext;
/**
* @brief Libère le contexte du dialogue de métadonnées.
*/
static void application_evidence_dialog_context_free(
gpointer user_data
)
{
ApplicationEvidenceDialogContext *context =
user_data;
if (context == NULL)
{
return;
}
g_free(
context->file_path
);
g_free(
context
);
}
/**
* @brief Libère le contexte dun import asynchrone.
*/
static void application_evidence_import_context_free(
gpointer user_data
)
{
ApplicationEvidenceImportContext *context =
user_data;
if (context == NULL)
{
return;
}
g_free(
context->investigation_root_path
);
g_free(
context
);
}
/**
* @brief Libère le contexte d'une vérification d'intégrité.
*/
static void application_evidence_integrity_context_free(
gpointer user_data
)
{
ApplicationEvidenceIntegrityContext *context =
user_data;
if (context == NULL)
{
return;
}
g_free(
context->original_name
);
g_free(
context->evidence_identifier
);
g_free(
context->database_path
);
g_free(
context->investigation_root_path
);
g_free(
context
);
}
/**
* @brief Libère le contexte d'un chargement de graphe.
*/
static void application_graph_load_context_free(
gpointer user_data
)
{
g_free(
user_data
);
}
/**
* @brief Annule et détache le chargement de graphe courant.
*
* Chaque appel invalide les résultats appartenant à la génération
* précédente, même lorsqu'aucune tâche n'est encore active.
*/
static void application_cancel_graph_loading(
Application *application
)
{
InvestigationGraphLoadTask *load_task =
NULL;
if (application == NULL)
{
return;
}
application->graph_load_generation++;
load_task =
application->graph_load_task;
application->graph_load_task =
NULL;
if (load_task == NULL)
{
return;
}
investigation_graph_load_task_cancel(
load_task
);
investigation_graph_load_task_free(
load_task
);
}
/**
* @brief Détache puis libère le graphe possédé par Application.
*/
static void application_clear_graph(
Application *application
)
{
InvestigationGraphModel *graph_model =
NULL;
InvestigationGraphLayout *graph_layout =
NULL;
if (application == NULL)
{
return;
}
if (application->main_window != NULL)
{
main_window_clear_graph(
application->main_window
);
}
graph_model =
application->graph_model;
graph_layout =
application->graph_layout;
application->graph_model =
NULL;
application->graph_layout =
NULL;
investigation_graph_model_free(
graph_model
);
investigation_graph_layout_free(
graph_layout
);
}
/**
* @brief Traite le résultat d'un chargement asynchrone de graphe.
*
* Le callback est exécuté sur le contexte principal GLib.
*/
static void application_on_graph_loaded(
InvestigationGraphLoadTask *load_task,
InvestigationGraphModel *graph_model,
const GError *error,
gpointer user_data
)
{
ApplicationGraphLoadContext *context =
user_data;
Application *application =
NULL;
InvestigationGraphLayout *graph_layout =
NULL;
char *status_message =
NULL;
gboolean is_current_result =
FALSE;
if (context != NULL)
{
application =
context->application;
}
graph_layout =
investigation_graph_load_task_take_layout(
load_task
);
if (application != NULL)
{
is_current_result =
load_task ==
application->graph_load_task &&
context->generation ==
application->graph_load_generation;
}
/*
* Un résultat appartenant à une ancienne enquête ne doit jamais
* modifier l'interface actuelle.
*/
if (!is_current_result)
{
investigation_graph_model_free(
graph_model
);
investigation_graph_layout_free(
graph_layout
);
investigation_graph_load_task_free(
load_task
);
return;
}
application->graph_load_task =
NULL;
if (error != NULL)
{
investigation_graph_model_free(
graph_model
);
investigation_graph_layout_free(
graph_layout
);
main_window_set_graph_error(
application->main_window,
error->message
);
main_window_set_status(
application->main_window,
"Le graphe de l'enquête n'a pas pu être chargé."
);
investigation_graph_load_task_free(
load_task
);
return;
}
if (graph_model == NULL ||
graph_layout == NULL)
{
investigation_graph_model_free(
graph_model
);
investigation_graph_layout_free(
graph_layout
);
main_window_set_graph_error(
application->main_window,
"Le chargement s'est terminé sans fournir "
"un graphe et une disposition valides."
);
main_window_set_status(
application->main_window,
"Le graphe de l'enquête est indisponible."
);
investigation_graph_load_task_free(
load_task
);
return;
}
/*
* MainWindow et Workspace doivent être détachés de l'ancien modèle
* avant sa libération.
*/
application_clear_graph(
application
);
application->graph_model =
graph_model;
application->graph_layout =
graph_layout;
main_window_set_graph(
application->main_window,
application->graph_model,
application->graph_layout
);
status_message =
g_strdup_printf(
"Graphe chargé : %" G_GUINT64_FORMAT
" entité(s), %" G_GUINT64_FORMAT
" relation(s), %u position(s) restaurée(s).",
investigation_graph_model_get_entity_count(
application->graph_model
),
investigation_graph_model_get_relation_count(
application->graph_model
),
investigation_graph_layout_get_count(
application->graph_layout
)
);
main_window_set_status(
application->main_window,
status_message != NULL
? status_message
: "Graphe chargé."
);
g_free(
status_message
);
investigation_graph_load_task_free(
load_task
);
}
/**
* @brief Démarre le chargement asynchrone du graphe d'une enquête.
*/
static void application_start_graph_loading(
Application *application,
const char *database_path
)
{
InvestigationGraphLoadTask *load_task =
NULL;
ApplicationGraphLoadContext *context =
NULL;
GError *error =
NULL;
if (application == NULL ||
application->main_window == NULL)
{
return;
}
/*
* Cette opération invalide l'ancienne génération et libère
* l'ancien chargement avant d'en créer un nouveau.
*/
application_cancel_graph_loading(
application
);
application_clear_graph(
application
);
if (database_path == NULL ||
database_path[0] == '\0')
{
main_window_set_graph_error(
application->main_window,
"Le chemin de la base SQLite est invalide."
);
return;
}
main_window_set_graph_loading(
application->main_window
);
load_task =
investigation_graph_load_task_new(
database_path,
&error
);
if (load_task == NULL)
{
main_window_set_graph_error(
application->main_window,
error != NULL
? error->message
: "Impossible de préparer le chargement du graphe."
);
g_clear_error(
&error
);
return;
}
context =
g_try_new0(
ApplicationGraphLoadContext,
1
);
if (context == NULL)
{
investigation_graph_load_task_free(
load_task
);
main_window_set_graph_error(
application->main_window,
"Impossible d'allouer le contexte de chargement."
);
return;
}
context->application =
application;
context->generation =
application->graph_load_generation;
application->graph_load_task =
load_task;
if (!investigation_graph_load_task_start(
load_task,
application_on_graph_loaded,
context,
application_graph_load_context_free,
&error
))
{
/*
* En cas d'échec immédiat, la tâche n'a pas pris possession
* du contexte fourni.
*/
application->graph_load_task =
NULL;
application_graph_load_context_free(
context
);
investigation_graph_load_task_free(
load_task
);
main_window_set_graph_error(
application->main_window,
error != NULL
? error->message
: "Impossible de démarrer le chargement du graphe."
);
g_clear_error(
&error
);
}
}
/**
* @brief Vérifie que lenquête active correspond à un chemin racine.
*/
static gboolean application_session_matches_root(
const Application *application,
const char *expected_root_path
)
{
const InvestigationProject *project = NULL;
const char *current_root_path = NULL;
if (application == NULL ||
application->session == NULL ||
expected_root_path == NULL)
{
return FALSE;
}
project =
investigation_session_get_project(
application->session
);
if (project == NULL)
{
return FALSE;
}
current_root_path =
investigation_project_get_root_path(
project
);
return g_strcmp0(
current_root_path,
expected_root_path
) == 0;
}
/**
* @brief Mémorise l'identifiant de la preuve actuellement affichée.
*/
static void application_set_selected_evidence_identifier(
Application *application,
const char *evidence_identifier
)
{
char *identifier_copy =
NULL;
if (application == NULL)
{
return;
}
if (evidence_identifier != NULL &&
g_uuid_string_is_valid(
evidence_identifier
))
{
identifier_copy =
g_strdup(
evidence_identifier
);
}
g_free(
application->selected_evidence_identifier
);
application->selected_evidence_identifier =
identifier_copy;
}
static void application_present_error(
Application *application,
const char *title,
const char *message
);
/**
* @brief Traite la fin de linitialisation des outils externes.
*
* Ce callback est exécuté sur le contexte principal GLib.
*
* @param tool_initializer Initialiseur ayant terminé.
* @param final_state État final de la tâche.
* @param summary Résumé emprunté en cas de réussite.
* @param error Erreur empruntée en cas déchec.
* @param user_data Pointeur emprunté vers Application.
*/
static void application_on_tool_initialization_completed(
ToolInitializer *tool_initializer,
BackgroundTaskState final_state,
const ToolInitializationSummary *summary,
const GError *error,
gpointer user_data
)
{
Application *application = user_data;
if (application == NULL ||
tool_initializer == NULL)
{
return;
}
if (tool_initializer !=
application->tool_initializer)
{
g_warning(
"Notification reçue depuis un initialiseur inconnu."
);
return;
}
switch (final_state)
{
case BACKGROUND_TASK_STATE_COMPLETED:
if (summary == NULL)
{
g_warning(
"Linitialisation des outils est terminée "
"sans résumé valide."
);
return;
}
g_message(
"Outils externes initialisés : "
"%" G_GSIZE_FORMAT " disponibles, "
"%" G_GSIZE_FORMAT " absents, "
"%" G_GSIZE_FORMAT " versions détectées, "
"%" G_GSIZE_FORMAT " versions non détectées.",
summary->available_count,
summary->missing_count,
summary->version_detected_count,
summary->version_failed_count
);
break;
case BACKGROUND_TASK_STATE_CANCELLED:
g_message(
"Linitialisation des outils externes a été annulée."
);
break;
case BACKGROUND_TASK_STATE_FAILED:
g_warning(
"Linitialisation des outils externes a échoué : %s",
error != NULL
? error->message
: "erreur inconnue"
);
application_present_error(
application,
"Initialisation des outils impossible",
error != NULL
? error->message
: "Les outils externes nont pas pu être initialisés."
);
break;
case BACKGROUND_TASK_STATE_PENDING:
case BACKGROUND_TASK_STATE_RUNNING:
default:
g_warning(
"Notification de fin reçue avec un état invalide : %d.",
(int) final_state
);
break;
}
}
/**
* @brief Démarre la détection asynchrone des outils externes.
*
* Une erreur empêche seulement linitialisation des outils.
* Elle ne doit pas fermer lapplication.
*
* @param application Application concernée.
*/
static void application_start_tool_initialization(
Application *application
)
{
GError *error = NULL;
if (application == NULL ||
application->tool_initializer == NULL)
{
return;
}
if (!tool_initializer_start(
application->tool_initializer,
&error
))
{
g_warning(
"Impossible dinitialiser les outils externes : %s",
error != NULL
? error->message
: "erreur inconnue"
);
application_present_error(
application,
"Initialisation des outils impossible",
error != NULL
? error->message
: "Les outils externes nont pas pu être initialisés."
);
g_clear_error(
&error
);
}
}
/**
* @brief Affiche une erreur dans une fenêtre GTK.
*
* @param application Application propriétaire de la fenêtre principale.
* @param title Titre compréhensible par l'utilisateur.
* @param message Description de l'erreur.
*/
static void application_present_error(
Application *application,
const char *title,
const char *message
)
{
GtkWindow *parent_window = NULL;
if (application != NULL &&
application->main_window != NULL)
{
parent_window = main_window_get_window(
application->main_window
);
}
application_message_dialog_present(
parent_window,
APPLICATION_MESSAGE_DIALOG_ERROR,
title,
message
);
}
static void application_on_tool_initialization_completed(
ToolInitializer *tool_initializer,
BackgroundTaskState final_state,
const ToolInitializationSummary *summary,
const GError *error,
gpointer user_data
);
static void application_present_error(
Application *application,
const char *title,
const char *message
);
/**
* @brief Charge les preuves de la session et construit leurs modèles GTK.
*
* Les nouveaux modèles sont entièrement préparés avant de remplacer
* ceux actuellement affichés.
*
* En cas d'échec, les anciens modèles restent la propriété de
* l'application et ne sont pas modifiés.
*/
static gboolean application_refresh_evidence_models(
Application *application,
GError **error
)
{
Database *database = NULL;
EvidenceDao *evidence_dao = NULL;
EvidenceTypeDao *evidence_type_dao = NULL;
GPtrArray *evidence_records = NULL;
GPtrArray *evidence_types = NULL;
GHashTable *type_labels = NULL;
EvidenceListModel *new_evidence_list_model =
NULL;
EvidenceCategoryModel *new_evidence_category_model =
NULL;
GError *local_error = NULL;
guint type_index = 0;
gboolean refreshed = FALSE;
g_return_val_if_fail(
error == NULL || *error == NULL,
FALSE
);
if (application == NULL ||
application->main_window == NULL ||
application->session == NULL)
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"L'application ou la session d'enquête est invalide."
);
return FALSE;
}
database =
investigation_session_get_database(
application->session
);
if (database == NULL)
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"La session ne fournit aucune connexion SQLite."
);
return FALSE;
}
evidence_dao =
evidence_dao_new(
database,
&local_error
);
if (evidence_dao == NULL)
{
if (local_error != NULL)
{
if (error != NULL)
{
g_propagate_prefixed_error(
error,
local_error,
"Impossible de créer le DAO des preuves : "
);
local_error = NULL;
}
else
{
g_clear_error(
&local_error
);
}
}
else
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"Impossible de créer le DAO des preuves."
);
}
goto cleanup;
}
evidence_records =
evidence_dao_list_all(
evidence_dao,
&local_error
);
if (evidence_records == NULL)
{
if (local_error != NULL)
{
if (error != NULL)
{
g_propagate_prefixed_error(
error,
local_error,
"Impossible de charger les preuves : "
);
local_error = NULL;
}
else
{
g_clear_error(
&local_error
);
}
}
else
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"Impossible de charger les preuves."
);
}
goto cleanup;
}
evidence_type_dao =
evidence_type_dao_new(
database,
&local_error
);
if (evidence_type_dao == NULL)
{
if (local_error != NULL)
{
if (error != NULL)
{
g_propagate_prefixed_error(
error,
local_error,
"Impossible de créer le DAO des types de preuve : "
);
local_error = NULL;
}
else
{
g_clear_error(
&local_error
);
}
}
else
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"Impossible de créer le DAO des types de preuve."
);
}
goto cleanup;
}
evidence_types =
evidence_type_dao_list_all(
evidence_type_dao,
&local_error
);
if (evidence_types == NULL)
{
if (local_error != NULL)
{
if (error != NULL)
{
g_propagate_prefixed_error(
error,
local_error,
"Impossible de charger les types de preuve : "
);
local_error = NULL;
}
else
{
g_clear_error(
&local_error
);
}
}
else
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"Impossible de charger les types de preuve."
);
}
goto cleanup;
}
type_labels =
g_hash_table_new_full(
g_str_hash,
g_str_equal,
g_free,
g_free
);
if (type_labels == NULL)
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"Impossible d'allouer la table des libellés."
);
goto cleanup;
}
for (type_index = 0;
type_index < evidence_types->len;
type_index++)
{
const EvidenceType *evidence_type =
NULL;
const char *type_code = NULL;
const char *type_label = NULL;
evidence_type =
g_ptr_array_index(
evidence_types,
type_index
);
if (evidence_type == NULL)
{
continue;
}
type_code =
evidence_type_get_code(
evidence_type
);
type_label =
evidence_type_get_label(
evidence_type
);
if (type_code == NULL ||
type_code[0] == '\0' ||
type_label == NULL ||
type_label[0] == '\0')
{
continue;
}
g_hash_table_replace(
type_labels,
g_strdup(
type_code
),
g_strdup(
type_label
)
);
}
new_evidence_list_model =
evidence_list_model_new();
new_evidence_category_model =
evidence_category_model_new();
if (new_evidence_list_model == NULL ||
new_evidence_category_model == NULL)
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"Impossible d'allouer les modèles d'affichage des preuves."
);
goto cleanup;
}
if (!evidence_list_model_replace(
new_evidence_list_model,
evidence_records,
&local_error
))
{
if (local_error != NULL)
{
if (error != NULL)
{
g_propagate_prefixed_error(
error,
local_error,
"Impossible de préparer la liste des preuves : "
);
local_error = NULL;
}
else
{
g_clear_error(
&local_error
);
}
}
goto cleanup;
}
if (!evidence_category_model_replace(
new_evidence_category_model,
evidence_list_model_get_list_model(
new_evidence_list_model
),
type_labels,
&local_error
))
{
if (local_error != NULL)
{
if (error != NULL)
{
g_propagate_prefixed_error(
error,
local_error,
"Impossible de regrouper les preuves : "
);
local_error = NULL;
}
else
{
g_clear_error(
&local_error
);
}
}
goto cleanup;
}
/*
* La vue doit être détachée avant la libération de son ancien modèle.
*/
main_window_set_evidence_model(
application->main_window,
NULL
);
evidence_category_model_free(
application->evidence_category_model
);
evidence_list_model_free(
application->evidence_list_model
);
application->evidence_list_model =
new_evidence_list_model;
application->evidence_category_model =
new_evidence_category_model;
new_evidence_list_model =
NULL;
new_evidence_category_model =
NULL;
main_window_set_evidence_model(
application->main_window,
application->evidence_category_model
);
refreshed = TRUE;
cleanup:
g_clear_error(
&local_error
);
evidence_category_model_free(
new_evidence_category_model
);
evidence_list_model_free(
new_evidence_list_model
);
if (type_labels != NULL)
{
g_hash_table_unref(
type_labels
);
}
if (evidence_types != NULL)
{
g_ptr_array_unref(
evidence_types
);
}
if (evidence_records != NULL)
{
g_ptr_array_unref(
evidence_records
);
}
evidence_type_dao_free(
evidence_type_dao
);
evidence_dao_free(
evidence_dao
);
return refreshed;
}
/**
* @brief Installe une nouvelle session et son arbre dans l'application.
*
* En cas de succès, Application devient propriétaire de new_session
* et de new_tree_model.
*
* En cas d'échec, le code appelant conserve la propriété des deux objets.
*
* @param application Application à mettre à jour.
* @param new_session Nouvelle session valide.
* @param new_tree_model Nouvel arbre valide.
*
* @return TRUE si l'installation réussit, sinon FALSE.
*/
static gboolean application_install_session(
Application *application,
InvestigationSession *new_session,
InvestigationTreeModel *new_tree_model
)
{
const InvestigationProject *project = NULL;
const InvestigationRecord *record = NULL;
const char *root_path = NULL;
const char *database_path = NULL;
const char *investigation_name = NULL;
GError *evidence_error = NULL;
if (application == NULL ||
application->main_window == NULL ||
new_session == NULL ||
new_tree_model == NULL)
{
return FALSE;
}
project = investigation_session_get_project(
new_session
);
record = investigation_session_get_record(
new_session
);
if (project == NULL ||
record == NULL)
{
return FALSE;
}
root_path = investigation_project_get_root_path(
project
);
database_path =
investigation_project_get_database_path(
project
);
investigation_name = investigation_record_get_name(
record
);
if (root_path == NULL ||
root_path[0] == '\0' ||
database_path == NULL ||
database_path[0] == '\0' ||
investigation_name == NULL ||
investigation_name[0] == '\0')
{
return FALSE;
}
application_set_selected_evidence_identifier(
application,
NULL
);
main_window_set_selected_evidence(
application->main_window,
NULL
);
/*
* Les nouveaux objets sont entièrement valides.
* Les anciens peuvent maintenant être libérés.
*/
investigation_tree_model_free(
application->tree_model
);
investigation_session_close(
application->session
);
application->tree_model = new_tree_model;
application->session = new_session;
main_window_set_tree_model(
application->main_window,
application->tree_model
);
main_window_set_investigation(
application->main_window,
investigation_name,
root_path
);
main_window_set_import_evidence_enabled(
application->main_window,
TRUE
);
application_start_graph_loading(
application,
database_path
);
/*
* L'ancienne liste ne doit jamais rester affichée après
* un changement d'enquête.
*/
main_window_set_evidence_model(
application->main_window,
NULL
);
evidence_category_model_free(
application->evidence_category_model
);
evidence_list_model_free(
application->evidence_list_model
);
application->evidence_category_model =
NULL;
application->evidence_list_model =
NULL;
if (!application_refresh_evidence_models(
application,
&evidence_error
))
{
g_warning(
"Impossible de charger les preuves de l'enquête : %s",
evidence_error != NULL
? evidence_error->message
: "erreur inconnue"
);
application_present_error(
application,
"Preuves indisponibles",
evidence_error != NULL
? evidence_error->message
: "Les preuves enregistrées n'ont pas pu être chargées."
);
g_clear_error(
&evidence_error
);
}
return TRUE;
}
/**
* @brief Ouvre une enquête et l'installe dans l'application.
*
* En cas de succès, Application devient propriétaire de la nouvelle
* session et du nouvel arbre.
*
* En cas d'échec, l'état précédent de l'application reste inchangé.
*
* @param application Application à mettre à jour.
* @param root_path Chemin racine de l'enquête.
* @param error Emplacement facultatif pour l'erreur.
*
* @return TRUE si l'enquête est ouverte et installée, sinon FALSE.
*/
static gboolean application_open_and_install_investigation(
Application *application,
const char *root_path,
GError **error
)
{
InvestigationSession *new_session = NULL;
InvestigationTreeModel *new_tree_model = NULL;
const InvestigationProject *project = NULL;
const char *canonical_root_path = NULL;
GError *session_error = NULL;
g_return_val_if_fail(
error == NULL || *error == NULL,
FALSE
);
if (application == NULL ||
application->main_window == NULL)
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_INVALID_ARGUMENT,
"L'application ou sa fenêtre principale est invalide."
);
return FALSE;
}
if (root_path == NULL ||
root_path[0] == '\0')
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_INVALID_ARGUMENT,
"Le chemin racine de l'enquête est invalide."
);
return FALSE;
}
new_session = investigation_session_open(
root_path,
&session_error
);
if (new_session == NULL)
{
if (session_error != NULL)
{
if (error != NULL)
{
g_propagate_prefixed_error(
error,
session_error,
"Impossible d'ouvrir la session : "
);
}
else
{
g_clear_error(
&session_error
);
}
}
else
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_INVALID_PROJECT,
"Impossible d'ouvrir la session de l'enquête."
);
}
return FALSE;
}
project = investigation_session_get_project(
new_session
);
if (project == NULL)
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_INVALID_PROJECT,
"La session ne fournit aucun projet valide."
);
investigation_session_close(
new_session
);
return FALSE;
}
canonical_root_path =
investigation_project_get_root_path(
project
);
if (canonical_root_path == NULL ||
canonical_root_path[0] == '\0')
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_INVALID_PROJECT,
"Le projet ne fournit aucun chemin racine valide."
);
investigation_session_close(
new_session
);
return FALSE;
}
new_tree_model = investigation_tree_builder_build(
canonical_root_path
);
if (new_tree_model == NULL)
{
g_set_error(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_TREE_BUILD,
"Impossible de construire l'arborescence de l'enquête "
"située dans '%s'.",
canonical_root_path
);
investigation_session_close(
new_session
);
return FALSE;
}
if (!application_install_session(
application,
new_session,
new_tree_model
))
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_INSTALL,
"Impossible d'installer la session dans l'application."
);
investigation_tree_model_free(
new_tree_model
);
investigation_session_close(
new_session
);
return FALSE;
}
/*
* La propriété de new_session et new_tree_model a été
* transférée à Application.
*/
return TRUE;
}
/**
* @brief Traite le dossier d'enquête sélectionné par l'utilisateur.
*
* @param folder_path Chemin sélectionné, ou NULL en cas d'annulation.
* @param user_data Pointeur vers Application.
*/
static void application_on_folder_selected(
const char *folder_path,
gpointer user_data
)
{
Application *application = user_data;
GError *error = NULL;
if (application == NULL)
{
return;
}
/*
* L'annulation du sélecteur ne constitue pas une erreur.
*/
if (folder_path == NULL)
{
return;
}
if (!application_open_and_install_investigation(
application,
folder_path,
&error
))
{
g_warning(
"Impossible d'ouvrir l'enquête '%s' : %s",
folder_path,
error != NULL
? error->message
: "erreur inconnue"
);
application_present_error(
application,
"Ouverture impossible",
error != NULL
? error->message
: "L'enquête sélectionnée n'a pas pu être ouverte."
);
g_clear_error(
&error
);
}
}
/**
* @brief Traite la demande de création d'une nouvelle enquête.
*
* @param parent_directory Dossier dans lequel créer l'enquête.
* @param investigation_name Nom nettoyé de l'enquête.
* @param user_data Pointeur vers Application.
*/
static void application_on_create_investigation(
const char *parent_directory,
const char *investigation_name,
gpointer user_data
)
{
Application *application = user_data;
char *created_root_path = NULL;
char *user_message = NULL;
GError *error = NULL;
if (application == NULL)
{
return;
}
/*
* Une annulation du dialogue transmet deux pointeurs NULL.
*/
if (parent_directory == NULL ||
investigation_name == NULL)
{
return;
}
created_root_path = investigation_project_create(
parent_directory,
investigation_name
);
if (created_root_path == NULL)
{
g_warning(
"Impossible de créer l'enquête '%s' dans '%s'.",
investigation_name,
parent_directory
);
user_message = g_strdup_printf(
"L'enquête « %s » n'a pas pu être créée dans :\n"
"%s\n\n"
"Vérifiez que le dossier existe et que vous disposez "
"des droits d'écriture nécessaires.",
investigation_name,
parent_directory
);
application_present_error(
application,
"Création impossible",
user_message
);
g_free(
user_message
);
return;
}
if (!application_open_and_install_investigation(
application,
created_root_path,
&error
))
{
g_warning(
"L'enquête a été créée dans '%s', mais son "
"ouverture a échoué : %s",
created_root_path,
error != NULL
? error->message
: "erreur inconnue"
);
user_message = g_strdup_printf(
"L'enquête a bien été créée sur le disque dans :\n"
"%s\n\n"
"Labfy Investigation n'a cependant pas pu l'ouvrir :\n"
"%s",
created_root_path,
error != NULL
? error->message
: "Aucun détail supplémentaire n'est disponible."
);
application_present_error(
application,
"Enquête créée mais non ouverte",
user_message
);
g_free(
user_message
);
g_clear_error(
&error
);
}
g_free(
created_root_path
);
}
/**
* @brief Ouvre le dialogue de création d'une enquête.
*
* @param user_data Pointeur vers Application.
*/
static void application_on_new_investigation_requested(
gpointer user_data
)
{
Application *application = user_data;
if (application == NULL ||
application->main_window == NULL)
{
return;
}
create_investigation_dialog_present(
main_window_get_window(
application->main_window
),
application_on_create_investigation,
application
);
}
/**
* @brief Ouvre le sélecteur d'une enquête existante.
*
* @param user_data Pointeur vers Application.
*/
static void application_on_open_investigation_requested(
gpointer user_data
)
{
Application *application = user_data;
if (application == NULL ||
application->main_window == NULL)
{
return;
}
folder_dialog_select_folder(
main_window_get_window(
application->main_window
),
application_on_folder_selected,
application
);
}
/**
* @brief Reconstruit et remplace larborescence active.
*
* Le nouveau modèle est entièrement construit avant que lancien
* soit détaché et libéré.
*
* @param application Application concernée.
* @param investigation_root_path Racine à parcourir.
*
* @return TRUE si le remplacement a réussi.
*/
static gboolean application_refresh_investigation_tree(
Application *application,
const char *investigation_root_path
)
{
InvestigationTreeModel *new_tree_model = NULL;
InvestigationTreeModel *old_tree_model = NULL;
if (application == NULL ||
application->main_window == NULL ||
investigation_root_path == NULL ||
investigation_root_path[0] == '\0')
{
return FALSE;
}
new_tree_model =
investigation_tree_builder_build(
investigation_root_path
);
if (new_tree_model == NULL)
{
return FALSE;
}
old_tree_model =
application->tree_model;
/*
* MainWindow transmet le nouveau modèle à la Sidebar.
*
* InvestigationTreeView détache dabord son ancien modèle GTK,
* puis construit le nouveau. Lancien modèle métier reste vivant
* pendant toute cette opération.
*/
main_window_set_tree_model(
application->main_window,
new_tree_model
);
application->tree_model =
new_tree_model;
/*
* La vue ne référence plus les anciens nœuds.
*/
investigation_tree_model_free(
old_tree_model
);
return TRUE;
}
/**
* @brief Traite la fin dune tâche dimport.
*
* @param task Tâche dimport terminée.
* @param user_data Contexte ApplicationEvidenceImportContext.
*/
static void application_on_evidence_import_completed(
BackgroundTask *task,
gpointer user_data
)
{
ApplicationEvidenceImportContext *context =
user_data;
Application *application = NULL;
BackgroundTaskState state;
EvidenceRecord *evidence_record = NULL;
const char *original_name = NULL;
char *status_message = NULL;
GError *error = NULL;
GError *evidence_refresh_error =
NULL;
gboolean tree_refreshed =
FALSE;
gboolean evidence_refreshed =
FALSE;
if (task == NULL ||
context == NULL)
{
return;
}
application =
context->application;
if (application == NULL ||
application->main_window == NULL)
{
return;
}
main_window_set_import_evidence_enabled(
application->main_window,
application->session != NULL
);
state =
background_task_get_state(
task
);
if (state ==
BACKGROUND_TASK_STATE_COMPLETED)
{
evidence_record =
background_task_get_result(
task
);
if (evidence_record == NULL)
{
application_present_error(
application,
"Import incomplet",
"La tâche est terminée sans fournir de preuve."
);
return;
}
original_name =
evidence_record_get_original_name(
evidence_record
);
/*
* L'utilisateur peut avoir changé d'enquête pendant
* l'exécution asynchrone de l'import.
*
* Dans ce cas, la preuve reste correctement importée dans
* l'ancienne enquête, mais l'interface actuelle ne doit pas
* être rafraîchie avec ses données.
*/
if (application_session_matches_root(
application,
context->investigation_root_path
))
{
tree_refreshed =
application_refresh_investigation_tree(
application,
context->investigation_root_path
);
evidence_refreshed =
application_refresh_evidence_models(
application,
&evidence_refresh_error
);
if (!tree_refreshed)
{
g_warning(
"La preuve a été importée, mais l'arborescence de '%s' "
"n'a pas pu être reconstruite.",
context->investigation_root_path
);
}
if (!evidence_refreshed)
{
g_warning(
"La preuve a été importée, mais la liste des preuves "
"n'a pas pu être actualisée : %s",
evidence_refresh_error != NULL
? evidence_refresh_error->message
: "erreur inconnue"
);
}
if (tree_refreshed &&
evidence_refreshed)
{
status_message =
g_strdup_printf(
"Preuve importée : %s",
original_name != NULL &&
original_name[0] != '\0'
? original_name
: "(nom indisponible)"
);
}
else if (!tree_refreshed &&
evidence_refreshed)
{
status_message =
g_strdup_printf(
"Preuve importée : %s — "
"arborescence non actualisée",
original_name != NULL &&
original_name[0] != '\0'
? original_name
: "(nom indisponible)"
);
}
else if (tree_refreshed &&
!evidence_refreshed)
{
status_message =
g_strdup_printf(
"Preuve importée : %s — "
"liste des preuves non actualisée",
original_name != NULL &&
original_name[0] != '\0'
? original_name
: "(nom indisponible)"
);
}
else
{
status_message =
g_strdup_printf(
"Preuve importée : %s — "
"interface non actualisée",
original_name != NULL &&
original_name[0] != '\0'
? original_name
: "(nom indisponible)"
);
}
main_window_set_status(
application->main_window,
status_message
);
g_clear_error(
&evidence_refresh_error
);
}
g_message(
"Preuve importée dans '%s' : %s",
context->investigation_root_path,
original_name != NULL &&
original_name[0] != '\0'
? original_name
: "(nom indisponible)"
);
g_free(
status_message
);
return;
}
if (state ==
BACKGROUND_TASK_STATE_CANCELLED)
{
if (application_session_matches_root(
application,
context->investigation_root_path
))
{
main_window_set_status(
application->main_window,
"Import de preuve annulé."
);
}
return;
}
error =
background_task_dup_error(
task
);
g_warning(
"Limport de la preuve a échoué : %s",
error != NULL
? error->message
: "erreur inconnue"
);
application_present_error(
application,
"Import impossible",
error != NULL
? error->message
: "La preuve na pas pu être importée."
);
g_clear_error(
&error
);
}
/**
* @brief Retourne le dossier de stockage associé à un type de preuve.
*
* La chaîne retournée est statique et ne doit pas être libérée.
*/
static const char *application_get_evidence_subdirectory(
const char *type_identifier
)
{
if (g_strcmp0(
type_identifier,
"screenshot"
) == 0)
{
return "Captures_Ecran";
}
if (g_strcmp0(
type_identifier,
"photo"
) == 0)
{
return "Photos";
}
if (g_strcmp0(
type_identifier,
"video"
) == 0)
{
return "Videos";
}
if (g_strcmp0(
type_identifier,
"email"
) == 0)
{
return "Emails";
}
if (g_strcmp0(
type_identifier,
"audio"
) == 0)
{
return "Audios";
}
if (g_strcmp0(
type_identifier,
"archive"
) == 0)
{
return "Archives";
}
if (g_strcmp0(
type_identifier,
"conversation"
) == 0)
{
return "Conversations";
}
/*
* Documents, textes, autres types et types inconnus.
*/
return "Documents";
}
/**
* @brief Prépare et démarre limport asynchrone dun fichier.
*/
static void application_start_evidence_import(
Application *application,
const char *file_path,
const char *type_identifier,
const char *collected_at,
const char *source,
const char *description
)
{
const InvestigationProject *project = NULL;
const char *root_path = NULL;
const char *database_path = NULL;
const char *evidence_subdirectory = NULL;
char *relative_directory = NULL;
EvidenceImportTaskRequest request =
{0};
ApplicationEvidenceImportContext *context =
NULL;
BackgroundTask *task = NULL;
char *destination_directory = NULL;
char *source_name = NULL;
char *status_message = NULL;
GError *error = NULL;
if (application == NULL ||
application->main_window == NULL ||
application->session == NULL ||
application->task_manager == NULL ||
file_path == NULL ||
file_path[0] == '\0' ||
type_identifier == NULL ||
type_identifier[0] == '\0' ||
collected_at == NULL ||
collected_at[0] == '\0')
{
return;
}
project =
investigation_session_get_project(
application->session
);
if (project == NULL)
{
application_present_error(
application,
"Import impossible",
"La session ne fournit aucun projet denquête."
);
return;
}
root_path =
investigation_project_get_root_path(
project
);
database_path =
investigation_project_get_database_path(
project
);
if (root_path == NULL ||
root_path[0] == '\0' ||
database_path == NULL ||
database_path[0] == '\0')
{
application_present_error(
application,
"Import impossible",
"Les chemins de lenquête sont invalides."
);
return;
}
evidence_subdirectory =
application_get_evidence_subdirectory(
type_identifier
);
relative_directory =
g_build_filename(
APPLICATION_EVIDENCE_ROOT_DIRECTORY,
evidence_subdirectory,
NULL
);
if (relative_directory == NULL)
{
application_present_error(
application,
"Import impossible",
"Impossible de construire le chemin relatif de la preuve."
);
return;
}
destination_directory =
g_build_filename(
root_path,
relative_directory,
NULL
);
if (destination_directory == NULL)
{
application_present_error(
application,
"Import impossible",
"Impossible de construire le dossier de destination."
);
g_free(
relative_directory
);
return;
}
/*
* Le dossier devrait exister dans une enquête normale.
* Cette création rend néanmoins limport robuste face à un dossier
* supprimé accidentellement.
*/
if (g_mkdir_with_parents(
destination_directory,
0700
) != 0)
{
status_message =
g_strdup_printf(
"Impossible de préparer le dossier '%s' : %s",
destination_directory,
g_strerror(errno)
);
application_present_error(
application,
"Import impossible",
status_message
);
g_free(
status_message
);
g_free(
destination_directory
);
return;
}
context =
g_try_new0(
ApplicationEvidenceImportContext,
1
);
if (context == NULL)
{
application_present_error(
application,
"Import impossible",
"Impossible dallouer le contexte de la tâche."
);
g_free(
destination_directory
);
return;
}
context->application =
application;
context->investigation_root_path =
g_strdup(
root_path
);
if (context->investigation_root_path == NULL)
{
application_evidence_import_context_free(
context
);
g_free(
destination_directory
);
application_present_error(
application,
"Import impossible",
"Impossible de conserver le chemin de lenquête."
);
return;
}
request.database_path =
database_path;
request.source_path =
file_path;
request.destination_directory =
destination_directory;
request.relative_directory =
relative_directory;
request.type_identifier =
type_identifier;
request.collected_at =
collected_at;
request.source =
source;
request.description =
description;
task =
evidence_import_task_start(
application->task_manager,
&request,
application_on_evidence_import_completed,
context,
application_evidence_import_context_free,
&error
);
if (task == NULL)
{
/*
* La tâche na pas pris possession du contexte.
*/
application_evidence_import_context_free(
context
);
application_present_error(
application,
"Import impossible",
error != NULL
? error->message
: "La tâche dimport na pas pu être démarrée."
);
g_clear_error(
&error
);
g_free(
destination_directory
);
g_free(
relative_directory
);
return;
}
/*
* Le TaskManager et le worker conservent désormais leurs références.
*/
background_task_unref(
task
);
main_window_set_import_evidence_enabled(
application->main_window,
FALSE
);
source_name =
g_path_get_basename(
file_path
);
status_message =
g_strdup_printf(
"Import en cours : %s",
source_name != NULL
? source_name
: file_path
);
main_window_set_status(
application->main_window,
status_message
);
g_free(
status_message
);
g_free(
source_name
);
g_free(
destination_directory
);
g_free(
relative_directory
);
}
/**
* @brief Traite la validation ou l'annulation des métadonnées.
*
* @param result Résultat possédé par ce callback, ou NULL.
* @param user_data Contexte ApplicationEvidenceDialogContext.
*/
static void application_on_evidence_metadata_completed(
EvidenceImportDialogResult *result,
gpointer user_data
)
{
ApplicationEvidenceDialogContext *context =
user_data;
Application *application = NULL;
if (context == NULL)
{
evidence_import_dialog_result_free(
result
);
return;
}
application =
context->application;
if (application == NULL ||
application->main_window == NULL)
{
evidence_import_dialog_result_free(
result
);
application_evidence_dialog_context_free(
context
);
return;
}
/*
* Une annulation du dialogue ne doit lancer aucune tâche.
*/
if (result == NULL)
{
main_window_set_status(
application->main_window,
"Import de preuve annulé."
);
application_evidence_dialog_context_free(
context
);
return;
}
application_start_evidence_import(
application,
context->file_path,
evidence_import_dialog_result_get_type_identifier(
result
),
evidence_import_dialog_result_get_collected_at(
result
),
evidence_import_dialog_result_get_source(
result
),
evidence_import_dialog_result_get_description(
result
)
);
evidence_import_dialog_result_free(
result
);
application_evidence_dialog_context_free(
context
);
}
/**
* @brief Traite le fichier de preuve sélectionné.
*
* @param file_path Chemin local sélectionné, ou NULL.
* @param error Erreur de sélection, ou NULL.
* @param user_data Pointeur vers Application.
*/
static void application_on_evidence_file_selected(
const char *file_path,
const GError *error,
gpointer user_data
)
{
Application *application =
user_data;
if (application == NULL ||
application->main_window == NULL)
{
return;
}
/*
* Une annulation ne constitue pas une erreur.
*/
if (file_path == NULL &&
error == NULL)
{
main_window_set_status(
application->main_window,
"Import de preuve annulé."
);
return;
}
if (error != NULL)
{
g_warning(
"Impossible de sélectionner le fichier : %s",
error->message
);
application_present_error(
application,
"Sélection impossible",
error->message
);
return;
}
if (file_path == NULL ||
file_path[0] == '\0')
{
application_present_error(
application,
"Sélection impossible",
"Le chemin du fichier sélectionné est invalide."
);
return;
}
Database *database = NULL;
EvidenceTypeDao *evidence_type_dao = NULL;
GPtrArray *evidence_types = NULL;
ApplicationEvidenceDialogContext *dialog_context =
NULL;
GError *dialog_error = NULL;
database =
investigation_session_get_database(
application->session
);
if (database == NULL)
{
application_present_error(
application,
"Import impossible",
"La session ne fournit aucune connexion SQLite."
);
return;
}
evidence_type_dao =
evidence_type_dao_new(
database,
&dialog_error
);
if (evidence_type_dao == NULL)
{
application_present_error(
application,
"Types de preuve indisponibles",
dialog_error != NULL
? dialog_error->message
: "Impossible de créer le DAO des types de preuve."
);
g_clear_error(
&dialog_error
);
return;
}
evidence_types =
evidence_type_dao_list_all(
evidence_type_dao,
&dialog_error
);
evidence_type_dao_free(
evidence_type_dao
);
if (evidence_types == NULL)
{
application_present_error(
application,
"Types de preuve indisponibles",
dialog_error != NULL
? dialog_error->message
: "Impossible de lire les types de preuve."
);
g_clear_error(
&dialog_error
);
return;
}
if (evidence_types->len == 0)
{
application_present_error(
application,
"Types de preuve indisponibles",
"Aucun type de preuve n'est enregistré dans la base."
);
g_ptr_array_unref(
evidence_types
);
return;
}
dialog_context =
g_try_new0(
ApplicationEvidenceDialogContext,
1
);
if (dialog_context == NULL)
{
application_present_error(
application,
"Import impossible",
"Impossible d'allouer le contexte du dialogue."
);
g_ptr_array_unref(
evidence_types
);
return;
}
dialog_context->application =
application;
dialog_context->file_path =
g_strdup(
file_path
);
if (dialog_context->file_path == NULL)
{
application_present_error(
application,
"Import impossible",
"Impossible de conserver le chemin du fichier."
);
application_evidence_dialog_context_free(
dialog_context
);
g_ptr_array_unref(
evidence_types
);
return;
}
if (!evidence_import_dialog_present(
main_window_get_window(
application->main_window
),
dialog_context->file_path,
evidence_types,
application_on_evidence_metadata_completed,
dialog_context,
&dialog_error
))
{
application_present_error(
application,
"Dialogue d'import impossible",
dialog_error != NULL
? dialog_error->message
: "Le dialogue de métadonnées n'a pas pu être ouvert."
);
g_clear_error(
&dialog_error
);
application_evidence_dialog_context_free(
dialog_context
);
}
g_ptr_array_unref(
evidence_types
);
}
/**
* @brief Traite la demande d'import d'une preuve.
*
* Le sélecteur de fichier sera ajouté à l'étape suivante.
*
* @param user_data Pointeur vers Application.
*/
static void application_on_import_evidence_requested(
gpointer user_data
)
{
Application *application = user_data;
if (application == NULL ||
application->main_window == NULL)
{
return;
}
if (application->session == NULL)
{
main_window_set_import_evidence_enabled(
application->main_window,
FALSE
);
return;
}
file_dialog_select_file(
main_window_get_window(
application->main_window
),
application_on_evidence_file_selected,
application
);
}
/**
* @brief Traite la sélection d'un nœud dans l'arborescence.
*
* @param node Nœud sélectionné, ou NULL si aucune sélection.
* @param user_data Pointeur vers Application.
*/
static void application_on_tree_node_selected(
const InvestigationNode *node,
gpointer user_data
)
{
Application *application = user_data;
const char *node_name = NULL;
InvestigationNodeType node_type;
if (application == NULL)
{
return;
}
main_window_set_selected_node(
application->main_window,
node
);
if (node == NULL)
{
g_print("Aucun nœud sélectionné.\n");
return;
}
node_name = investigation_node_get_name(
node
);
node_type = investigation_node_get_type(
node
);
g_print(
"Nœud sélectionné : %s\n",
node_name != NULL
? node_name
: "(sans nom)"
);
if (node_type == INVESTIGATION_NODE_DIRECTORY)
{
g_print("Type : dossier\n");
}
else
{
g_print("Type : fichier\n");
}
}
/**
* @brief Traite la sélection d'une preuve dans la Sidebar.
*
* L'identifiant est emprunté uniquement pendant cet appel.
* La preuve complète est relue depuis SQLite avant d'être transmise
* au Workspace.
*
* @param evidence_identifier UUID de la preuve, ou NULL.
* @param user_data Pointeur vers Application.
*/
static void application_on_evidence_selected(
const char *evidence_identifier,
gpointer user_data
)
{
Application *application =
user_data;
Database *database =
NULL;
EvidenceDao *evidence_dao =
NULL;
EvidenceRecord *evidence_record =
NULL;
const char *original_name =
NULL;
char *status_message =
NULL;
GError *error =
NULL;
if (application == NULL ||
application->main_window == NULL)
{
return;
}
/*
* Une catégorie ou une sélection vide transmet NULL.
*/
if (evidence_identifier == NULL ||
evidence_identifier[0] == '\0')
{
application_set_selected_evidence_identifier(
application,
NULL
);
main_window_set_selected_evidence(
application->main_window,
NULL
);
application_set_selected_evidence_identifier(
application,
NULL
);
return;
}
if (application->session == NULL)
{
main_window_set_selected_evidence(
application->main_window,
NULL
);
return;
}
database =
investigation_session_get_database(
application->session
);
if (database == NULL)
{
main_window_set_selected_evidence(
application->main_window,
NULL
);
application_present_error(
application,
"Preuve indisponible",
"La session ne fournit aucune connexion SQLite."
);
return;
}
evidence_dao =
evidence_dao_new(
database,
&error
);
if (evidence_dao == NULL)
{
g_warning(
"Impossible de créer le DAO des preuves : %s",
error != NULL
? error->message
: "erreur inconnue"
);
main_window_set_selected_evidence(
application->main_window,
NULL
);
application_present_error(
application,
"Preuve indisponible",
error != NULL
? error->message
: "Impossible d'accéder aux preuves enregistrées."
);
g_clear_error(
&error
);
return;
}
evidence_record =
evidence_dao_find_by_identifier(
evidence_dao,
evidence_identifier,
&error
);
evidence_dao_free(
evidence_dao
);
if (error != NULL)
{
g_warning(
"Impossible de charger la preuve '%s' : %s",
evidence_identifier,
error->message
);
main_window_set_selected_evidence(
application->main_window,
NULL
);
application_present_error(
application,
"Preuve indisponible",
error->message
);
g_clear_error(
&error
);
return;
}
/*
* Une preuve absente retourne NULL sans erreur.
*/
if (evidence_record == NULL)
{
g_warning(
"La preuve '%s' n'existe plus dans la base.",
evidence_identifier
);
main_window_set_selected_evidence(
application->main_window,
NULL
);
main_window_set_status(
application->main_window,
"La preuve sélectionnée est introuvable."
);
return;
}
application_set_selected_evidence_identifier(
application,
evidence_identifier
);
/*
* Workspace copie immédiatement les valeurs dans ses widgets GTK.
*/
main_window_set_selected_evidence(
application->main_window,
evidence_record
);
original_name =
evidence_record_get_original_name(
evidence_record
);
status_message =
g_strdup_printf(
"Preuve sélectionnée : %s",
original_name != NULL &&
original_name[0] != '\0'
? original_name
: "(sans nom)"
);
main_window_set_status(
application->main_window,
status_message
);
g_free(
status_message
);
evidence_record_free(
evidence_record
);
}
/**
* @brief Traite la validation ou l'annulation du dialogue de relation.
*
* Cette première étape valide le parcours complet de l'interface.
* L'écriture SQLite sera branchée après validation de la compilation.
*/
static void application_on_create_relation_completed(
CreateRelationDialogResult *result,
gpointer user_data
)
{
Application *application =
user_data;
const char *source_identifier =
NULL;
const char *target_identifier =
NULL;
const char *relation_type =
NULL;
char *status_message =
NULL;
if (application == NULL ||
application->main_window == NULL)
{
create_relation_dialog_result_free(
result
);
return;
}
if (result == NULL)
{
main_window_set_status(
application->main_window,
"Ajout de relation annulé."
);
return;
}
source_identifier =
create_relation_dialog_result_get_source_identifier(
result
);
target_identifier =
create_relation_dialog_result_get_target_identifier(
result
);
relation_type =
create_relation_dialog_result_get_relation_type(
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)"
);
main_window_set_status(
application->main_window,
status_message != NULL
? status_message
: "Relation préparée."
);
g_free(
status_message
);
create_relation_dialog_result_free(
result
);
}
/**
* @brief Ouvre le dialogue de création depuis la fiche d'une entité.
*/
static void application_on_add_relation_requested(
const char *source_entity_identifier,
gpointer user_data
)
{
Application *application =
user_data;
Database *database =
NULL;
EntityDao *entity_dao =
NULL;
GPtrArray *entities =
NULL;
GError *error =
NULL;
if (application == NULL ||
application->main_window == NULL ||
application->session == NULL ||
source_entity_identifier == NULL ||
!g_uuid_string_is_valid(
source_entity_identifier
))
{
return;
}
database =
investigation_session_get_database(
application->session
);
if (database == NULL)
{
application_present_error(
application,
"Relations indisponibles",
"La session ne fournit aucune connexion SQLite."
);
return;
}
entity_dao =
entity_dao_new(
database,
&error
);
if (entity_dao == NULL)
{
application_present_error(
application,
"Relations indisponibles",
error != NULL
? error->message
: "Impossible d'accéder aux entités."
);
g_clear_error(
&error
);
return;
}
entities =
entity_dao_list_all(
entity_dao,
&error
);
entity_dao_free(
entity_dao
);
if (entities == NULL)
{
application_present_error(
application,
"Relations indisponibles",
error != NULL
? error->message
: "Impossible de charger les entités."
);
g_clear_error(
&error
);
return;
}
if (!create_relation_dialog_present(
main_window_get_window(
application->main_window
),
source_entity_identifier,
entities,
application_on_create_relation_completed,
application,
&error
))
{
application_present_error(
application,
"Ajout de relation impossible",
error != NULL
? error->message
: "Le dialogue de relation n'a pas pu être ouvert."
);
g_clear_error(
&error
);
}
g_ptr_array_unref(
entities
);
}
/**
* @brief Enregistre le statut d'intégrité dans l'enquête d'origine.
*
* Une connexion indépendante est utilisée afin que la bonne base soit
* mise à jour même si l'utilisateur a changé d'enquête entre-temps.
*/
static gboolean application_persist_evidence_integrity_status(
const ApplicationEvidenceIntegrityContext *context,
EvidenceIntegrityStatus integrity_status,
GError **error
)
{
Database *database =
NULL;
EvidenceDao *evidence_dao =
NULL;
gboolean updated =
FALSE;
g_return_val_if_fail(
error == NULL || *error == NULL,
FALSE
);
if (context == NULL ||
context->database_path == NULL ||
context->database_path[0] == '\0' ||
context->evidence_identifier == NULL ||
!g_uuid_string_is_valid(
context->evidence_identifier
))
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"Le contexte de mise à jour de l'intégrité est invalide."
);
return FALSE;
}
database =
database_open(
context->database_path
);
if (database == NULL)
{
g_set_error(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"Impossible d'ouvrir la base SQLite : %s",
context->database_path
);
return FALSE;
}
evidence_dao =
evidence_dao_new(
database,
error
);
if (evidence_dao == NULL)
{
database_close(
database
);
return FALSE;
}
updated =
evidence_dao_update_integrity_status(
evidence_dao,
context->evidence_identifier,
integrity_status,
error
);
evidence_dao_free(
evidence_dao
);
database_close(
database
);
return updated;
}
/**
* @brief Traite la fin d'une vérification d'intégrité.
*/
static void application_on_evidence_integrity_completed(
BackgroundTask *task,
gpointer user_data
)
{
ApplicationEvidenceIntegrityContext *context =
user_data;
Application *application =
NULL;
const EvidenceIntegrityVerificationResult *result =
NULL;
EvidenceIntegrityStatus integrity_status =
EVIDENCE_INTEGRITY_STATUS_ERROR;
BackgroundTaskState task_state;
const char *diagnostic =
NULL;
const char *display_name =
NULL;
char *status_message =
NULL;
GError *error =
NULL;
GError *refresh_error =
NULL;
gboolean session_matches =
FALSE;
gboolean evidence_was_selected =
FALSE;
if (task == NULL ||
context == NULL)
{
return;
}
application =
context->application;
if (application == NULL)
{
return;
}
task_state =
background_task_get_state(
task
);
session_matches =
application_session_matches_root(
application,
context->investigation_root_path
);
display_name =
context->original_name != NULL &&
context->original_name[0] != '\0'
? context->original_name
: context->evidence_identifier;
if (task_state ==
BACKGROUND_TASK_STATE_COMPLETED)
{
result =
background_task_get_result(
task
);
if (result == NULL)
{
if (session_matches)
{
application_present_error(
application,
"Vérification incomplète",
"La tâche s'est terminée sans fournir de résultat."
);
}
return;
}
integrity_status =
evidence_integrity_verification_result_get_status(
result
);
/*
* UNKNOWN après une vérification complète serait incohérent.
*/
if (integrity_status ==
EVIDENCE_INTEGRITY_STATUS_UNKNOWN)
{
integrity_status =
EVIDENCE_INTEGRITY_STATUS_ERROR;
}
diagnostic =
evidence_integrity_verification_result_get_diagnostic(
result
);
if (!application_persist_evidence_integrity_status(
context,
integrity_status,
&error
))
{
g_warning(
"Impossible d'enregistrer le statut de la preuve '%s' : %s",
context->evidence_identifier,
error != NULL
? error->message
: "erreur inconnue"
);
if (session_matches)
{
application_present_error(
application,
"Statut non enregistré",
error != NULL
? error->message
: "Le résultat n'a pas pu être enregistré."
);
}
g_clear_error(
&error
);
return;
}
if (session_matches)
{
/*
* La reconstruction du modèle peut provoquer temporairement
* une sélection vide. On mémorise donc l'état avant celle-ci.
*/
evidence_was_selected =
g_strcmp0(
application->selected_evidence_identifier,
context->evidence_identifier
) == 0;
if (!application_refresh_evidence_models(
application,
&refresh_error
))
{
g_warning(
"Le statut a été enregistré, mais la liste "
"des preuves n'a pas été actualisée : %s",
refresh_error != NULL
? refresh_error->message
: "erreur inconnue"
);
}
g_clear_error(
&refresh_error
);
/*
* On ne force pas l'affichage d'une ancienne preuve lorsque
* l'utilisateur en a sélectionné une autre entre-temps.
*/
if (evidence_was_selected)
{
application_on_evidence_selected(
context->evidence_identifier,
application
);
}
switch (integrity_status)
{
case EVIDENCE_INTEGRITY_STATUS_VALID:
status_message =
g_strdup_printf(
"Intégrité valide : %s",
display_name
);
break;
case EVIDENCE_INTEGRITY_STATUS_MODIFIED:
status_message =
g_strdup_printf(
"Alerte d'intégrité — fichier modifié : %s",
display_name
);
break;
case EVIDENCE_INTEGRITY_STATUS_MISSING:
status_message =
g_strdup_printf(
"Preuve absente du disque : %s",
display_name
);
break;
case EVIDENCE_INTEGRITY_STATUS_ERROR:
case EVIDENCE_INTEGRITY_STATUS_UNKNOWN:
default:
status_message =
g_strdup_printf(
"Erreur de vérification : %s",
display_name
);
break;
}
main_window_set_status(
application->main_window,
status_message
);
if (integrity_status ==
EVIDENCE_INTEGRITY_STATUS_ERROR &&
diagnostic != NULL &&
diagnostic[0] != '\0')
{
application_present_error(
application,
"Vérification impossible",
diagnostic
);
}
}
g_message(
"Vérification de '%s' terminée avec le statut %d.",
context->evidence_identifier,
(int) integrity_status
);
g_free(
status_message
);
return;
}
if (task_state ==
BACKGROUND_TASK_STATE_CANCELLED)
{
if (session_matches)
{
main_window_set_status(
application->main_window,
"Vérification d'intégrité annulée."
);
}
return;
}
error =
background_task_dup_error(
task
);
g_warning(
"La vérification de la preuve '%s' a échoué : %s",
context->evidence_identifier,
error != NULL
? error->message
: "erreur inconnue"
);
if (session_matches)
{
application_present_error(
application,
"Vérification impossible",
error != NULL
? error->message
: "La vérification n'a pas pu être exécutée."
);
}
g_clear_error(
&error
);
}
/**
* @brief Charge la preuve et démarre sa vérification asynchrone.
*/
static void application_start_evidence_integrity_verification(
Application *application,
const char *evidence_identifier
)
{
const InvestigationProject *project =
NULL;
Database *database =
NULL;
EvidenceDao *evidence_dao =
NULL;
EvidenceRecord *evidence_record =
NULL;
ApplicationEvidenceIntegrityContext *context =
NULL;
EvidenceIntegrityTaskRequest request =
{0};
BackgroundTask *task =
NULL;
const char *root_path =
NULL;
const char *database_path =
NULL;
const char *relative_path =
NULL;
const char *expected_sha256 =
NULL;
const char *original_name =
NULL;
char *status_message =
NULL;
GError *error =
NULL;
if (application == NULL ||
application->main_window == NULL ||
application->session == NULL ||
application->task_manager == NULL ||
evidence_identifier == NULL ||
!g_uuid_string_is_valid(
evidence_identifier
))
{
return;
}
project =
investigation_session_get_project(
application->session
);
database =
investigation_session_get_database(
application->session
);
if (project == NULL ||
database == NULL)
{
application_present_error(
application,
"Vérification impossible",
"La session d'enquête est invalide."
);
return;
}
root_path =
investigation_project_get_root_path(
project
);
database_path =
investigation_project_get_database_path(
project
);
if (root_path == NULL ||
root_path[0] == '\0' ||
database_path == NULL ||
database_path[0] == '\0')
{
application_present_error(
application,
"Vérification impossible",
"Les chemins de l'enquête sont invalides."
);
return;
}
evidence_dao =
evidence_dao_new(
database,
&error
);
if (evidence_dao == NULL)
{
application_present_error(
application,
"Vérification impossible",
error != NULL
? error->message
: "Impossible d'accéder aux preuves."
);
g_clear_error(
&error
);
return;
}
evidence_record =
evidence_dao_find_by_identifier(
evidence_dao,
evidence_identifier,
&error
);
evidence_dao_free(
evidence_dao
);
evidence_dao =
NULL;
if (error != NULL)
{
application_present_error(
application,
"Vérification impossible",
error->message
);
g_clear_error(
&error
);
return;
}
if (evidence_record == NULL)
{
application_present_error(
application,
"Preuve introuvable",
"La preuve n'existe plus dans la base SQLite."
);
return;
}
relative_path =
evidence_record_get_relative_path(
evidence_record
);
expected_sha256 =
evidence_record_get_sha256(
evidence_record
);
original_name =
evidence_record_get_original_name(
evidence_record
);
if (relative_path == NULL ||
relative_path[0] == '\0' ||
expected_sha256 == NULL ||
expected_sha256[0] == '\0')
{
application_present_error(
application,
"Vérification impossible",
"La preuve ne contient pas les données nécessaires."
);
evidence_record_free(
evidence_record
);
return;
}
context =
g_try_new0(
ApplicationEvidenceIntegrityContext,
1
);
if (context == NULL)
{
application_present_error(
application,
"Vérification impossible",
"Impossible d'allouer le contexte de la tâche."
);
evidence_record_free(
evidence_record
);
return;
}
context->application =
application;
context->investigation_root_path =
g_strdup(
root_path
);
context->database_path =
g_strdup(
database_path
);
context->evidence_identifier =
g_strdup(
evidence_identifier
);
context->original_name =
g_strdup(
original_name
);
if (context->investigation_root_path == NULL ||
context->database_path == NULL ||
context->evidence_identifier == NULL)
{
application_evidence_integrity_context_free(
context
);
evidence_record_free(
evidence_record
);
application_present_error(
application,
"Vérification impossible",
"Impossible de copier les paramètres de la tâche."
);
return;
}
request.investigation_root_path =
root_path;
request.relative_path =
relative_path;
request.expected_sha256 =
expected_sha256;
task =
evidence_integrity_task_start(
application->task_manager,
&request,
application_on_evidence_integrity_completed,
context,
application_evidence_integrity_context_free,
&error
);
if (task == NULL)
{
/*
* La propriété du contexte n'a pas été transférée.
*/
application_evidence_integrity_context_free(
context
);
application_present_error(
application,
"Vérification impossible",
error != NULL
? error->message
: "La tâche n'a pas pu être démarrée."
);
g_clear_error(
&error
);
evidence_record_free(
evidence_record
);
return;
}
/*
* BackgroundTask et TaskManager possèdent désormais leurs références.
*/
background_task_unref(
task
);
status_message =
g_strdup_printf(
"Vérification en cours : %s",
original_name != NULL &&
original_name[0] != '\0'
? original_name
: evidence_identifier
);
main_window_set_status(
application->main_window,
status_message
);
g_free(
status_message
);
evidence_record_free(
evidence_record
);
}
/**
* @brief Reçoit la demande de vérification d'une preuve.
*
* Cette première étape valide uniquement le relais de l'interface.
* Le lancement de la tâche sera ajouté ensuite.
*
* @param evidence_identifier UUID de la preuve.
* @param user_data Pointeur vers Application.
*/
/**
* @brief Traite la demande de vérification provenant du Workspace.
*/
static void application_on_verify_evidence_requested(
const char *evidence_identifier,
gpointer user_data
)
{
Application *application =
user_data;
application_start_evidence_integrity_verification(
application,
evidence_identifier
);
}
/**
* @brief Ferme proprement l'application.
*
* @param user_data Pointeur vers Application.
*/
/**
* @brief Enregistre la nouvelle position logique d'un nœud.
*
* Le modèle en mémoire est mis à jour avant l'UPSERT SQLite. En cas d'échec
* SQLite, le déplacement reste visible pendant la session, mais un message
* explicite indique qu'il ne survivra pas à la réouverture de l'enquête.
*/
static void application_on_graph_node_moved(
const char *entity_identifier,
double x,
double y,
gpointer user_data
)
{
Application *application =
user_data;
Database *database =
NULL;
GraphNodePositionDao *position_dao =
NULL;
GError *error =
NULL;
if (application == NULL ||
application->main_window == NULL ||
application->session == NULL ||
application->graph_layout == NULL ||
entity_identifier == NULL ||
!g_uuid_string_is_valid(
entity_identifier
))
{
return;
}
if (!investigation_graph_layout_set_position(
application->graph_layout,
entity_identifier,
x,
y,
&error
))
{
g_warning(
"Impossible de mettre à jour la position du nœud '%s' : %s",
entity_identifier,
error != NULL
? error->message
: "erreur inconnue"
);
application_present_error(
application,
"Position invalide",
error != NULL
? error->message
: "La position du nœud ne peut pas être conservée."
);
g_clear_error(
&error
);
return;
}
database =
investigation_session_get_database(
application->session
);
if (database == NULL)
{
main_window_set_status(
application->main_window,
"Nœud déplacé, mais position non enregistrée."
);
application_present_error(
application,
"Enregistrement impossible",
"La session ne fournit aucune connexion SQLite."
);
return;
}
position_dao =
graph_node_position_dao_new(
database,
&error
);
if (position_dao == NULL)
{
g_warning(
"Impossible de créer le DAO des positions : %s",
error != NULL
? error->message
: "erreur inconnue"
);
main_window_set_status(
application->main_window,
"Nœud déplacé, mais position non enregistrée."
);
application_present_error(
application,
"Enregistrement impossible",
error != NULL
? error->message
: "Impossible d'accéder aux positions du graphe."
);
g_clear_error(
&error
);
return;
}
if (!graph_node_position_dao_upsert(
position_dao,
entity_identifier,
x,
y,
&error
))
{
g_warning(
"Impossible d'enregistrer la position du nœud '%s' : %s",
entity_identifier,
error != NULL
? error->message
: "erreur inconnue"
);
main_window_set_status(
application->main_window,
"Nœud déplacé, mais position non enregistrée."
);
application_present_error(
application,
"Enregistrement impossible",
error != NULL
? error->message
: "La position du nœud n'a pas pu être enregistrée."
);
g_clear_error(
&error
);
graph_node_position_dao_free(
position_dao
);
return;
}
graph_node_position_dao_free(
position_dao
);
main_window_set_status(
application->main_window,
"Position du nœud enregistrée."
);
}
/**
* @brief Supprime les positions persistées et restaure la grille automatique.
*
* Cette fonction est appelée uniquement après une confirmation explicite.
* L'affichage et le modèle en mémoire restent inchangés en cas d'échec SQLite.
*/
static void application_reset_graph_layout(
Application *application
)
{
Database *database =
NULL;
GraphNodePositionDao *position_dao =
NULL;
GError *error =
NULL;
if (application == NULL ||
application->main_window == NULL ||
application->session == NULL ||
application->graph_model == NULL ||
application->graph_layout == NULL)
{
return;
}
database =
investigation_session_get_database(
application->session
);
if (database == NULL)
{
application_present_error(
application,
"Réinitialisation impossible",
"La session ne fournit aucune connexion SQLite."
);
return;
}
position_dao =
graph_node_position_dao_new(
database,
&error
);
if (position_dao == NULL)
{
g_warning(
"Impossible de créer le DAO des positions : %s",
error != NULL
? error->message
: "erreur inconnue"
);
application_present_error(
application,
"Réinitialisation impossible",
error != NULL
? error->message
: "Impossible d'accéder aux positions enregistrées."
);
g_clear_error(
&error
);
return;
}
if (!graph_node_position_dao_delete_all(
position_dao,
&error
))
{
g_warning(
"Impossible de supprimer les positions du graphe : %s",
error != NULL
? error->message
: "erreur inconnue"
);
application_present_error(
application,
"Réinitialisation impossible",
error != NULL
? error->message
: "Les positions enregistrées n'ont pas pu être supprimées."
);
g_clear_error(
&error
);
graph_node_position_dao_free(
position_dao
);
return;
}
graph_node_position_dao_free(
position_dao
);
investigation_graph_layout_clear(
application->graph_layout
);
main_window_reset_graph_layout(
application->main_window
);
main_window_set_status(
application->main_window,
"Disposition du graphe réinitialisée."
);
}
/**
* @brief Traite la réponse du dialogue de confirmation.
*/
static void application_on_reset_graph_layout_confirmed(
gboolean confirmed,
gpointer user_data
)
{
Application *application =
user_data;
if (!confirmed)
{
return;
}
application_reset_graph_layout(
application
);
}
/**
* @brief Demande confirmation avant toute suppression de position.
*/
static void application_on_show_graph_requested(
gpointer user_data
)
{
Application *application =
user_data;
if (application == NULL ||
application->main_window == NULL ||
application->graph_model == NULL)
{
return;
}
application_set_selected_evidence_identifier(
application,
NULL
);
/*
* Passer NULL restaure l'état principal du Workspace. Quand le graphe
* est prêt, workspace_show_default() affiche automatiquement le canvas.
*/
main_window_set_selected_evidence(
application->main_window,
NULL
);
main_window_set_status(
application->main_window,
"Graphe de l'enquête affiché."
);
}
/**
* @brief Demande confirmation avant toute suppression de position.
*/
static void application_on_reset_graph_layout_requested(
gpointer user_data
)
{
Application *application =
user_data;
if (application == NULL ||
application->main_window == NULL ||
application->session == NULL ||
application->graph_model == NULL ||
application->graph_layout == NULL)
{
return;
}
application_message_dialog_present_confirmation(
main_window_get_window(
application->main_window
),
APPLICATION_MESSAGE_DIALOG_WARNING,
"Réinitialiser la disposition du graphe",
"Toutes les positions personnalisées des nœuds seront supprimées. "
"Le graphe retrouvera sa disposition automatique.\n\n"
"Cette action ne peut pas être annulée.",
"Réinitialiser",
application_on_reset_graph_layout_confirmed,
application
);
}
/**
* @brief Ferme proprement l'application.
*
* @param user_data Pointeur vers Application.
*/
static void application_on_quit_requested(
gpointer user_data
)
{
Application *application = user_data;
if (application == NULL ||
application->gtk_application == NULL)
{
return;
}
task_manager_cancel_all(
application->task_manager
);
g_application_quit(
G_APPLICATION(
application->gtk_application
)
);
}
/**
* @brief Crée la fenêtre principale lors de l'activation.
*
* Le signal activate peut être reçu plusieurs fois. Si la fenêtre existe
* déjà, elle est simplement présentée.
*
* @param gtk_application Application GTK ayant reçu le signal.
* @param user_data Pointeur vers Application.
*/
static void application_on_activate(
GtkApplication *gtk_application,
gpointer user_data
)
{
Application *application = user_data;
if (application == NULL)
{
return;
}
if (application->main_window != NULL)
{
main_window_present(
application->main_window
);
return;
}
application->main_window = main_window_new(
gtk_application,
application->task_manager
);
if (application->main_window == NULL)
{
g_warning(
"Impossible de créer la fenêtre principale."
);
return;
}
main_window_set_tree_selection_callback(
application->main_window,
application_on_tree_node_selected,
application
);
main_window_set_evidence_selection_callback(
application->main_window,
application_on_evidence_selected,
application
);
main_window_set_verify_evidence_callback(
application->main_window,
application_on_verify_evidence_requested,
application
);
main_window_set_graph_node_moved_callback(
application->main_window,
application_on_graph_node_moved,
application
);
main_window_set_reset_graph_layout_callback(
application->main_window,
application_on_reset_graph_layout_requested,
application
);
main_window_set_show_graph_callback(
application->main_window,
application_on_show_graph_requested,
application
);
main_window_set_add_relation_callback(
application->main_window,
application_on_add_relation_requested,
application
);
main_window_set_new_investigation_callback(
application->main_window,
application_on_new_investigation_requested,
application
);
main_window_set_open_investigation_callback(
application->main_window,
application_on_open_investigation_requested,
application
);
main_window_set_import_evidence_callback(
application->main_window,
application_on_import_evidence_requested,
application
);
main_window_set_import_evidence_enabled(
application->main_window,
application->session != NULL
);
main_window_set_quit_callback(
application->main_window,
application_on_quit_requested,
application
);
main_window_present(
application->main_window
);
/*
* Le démarrage est non bloquant. Le worker sexécute dans un
* thread secondaire et sa progression apparaît dans TaskPanel.
*/
application_start_tool_initialization(
application
);
}
Application *application_new(void)
{
Application *application = NULL;
GError *error = NULL;
application = g_new0(
Application,
1
);
application->task_manager =
task_manager_new();
if (application->task_manager == NULL)
{
g_free(
application
);
return NULL;
}
application->tool_initializer =
tool_initializer_new(
application->task_manager,
&error
);
if (application->tool_initializer == NULL)
{
g_warning(
"Impossible de créer linitialiseur des outils : %s",
error != NULL
? error->message
: "erreur inconnue"
);
g_clear_error(
&error
);
task_manager_free(
application->task_manager
);
g_free(
application
);
return NULL;
}
tool_initializer_set_completed_callback(
application->tool_initializer,
application_on_tool_initialization_completed,
application,
NULL
);
application->gtk_application = gtk_application_new(
APPLICATION_ID,
G_APPLICATION_DEFAULT_FLAGS
);
if (application->gtk_application == NULL)
{
tool_initializer_free(
application->tool_initializer
);
task_manager_free(
application->task_manager
);
g_free(
application
);
return NULL;
}
g_signal_connect(
application->gtk_application,
"activate",
G_CALLBACK(application_on_activate),
application
);
return application;
}
int application_run(
Application *application,
int argc,
char **argv
)
{
if (application == NULL ||
application->gtk_application == NULL)
{
return 1;
}
return g_application_run(
G_APPLICATION(
application->gtk_application
),
argc,
argv
);
}
const InvestigationSession *application_get_session(
const Application *application
)
{
if (application == NULL)
{
return NULL;
}
return application->session;
}
void application_free(
Application *application
)
{
if (application == NULL)
{
return;
}
if (application->tool_initializer != NULL)
{
tool_initializer_set_completed_callback(
application->tool_initializer,
NULL,
NULL,
NULL
);
}
task_manager_cancel_all(
application->task_manager
);
application_cancel_graph_loading(
application
);
application_clear_graph(
application
);
investigation_tree_model_free(
application->tree_model
);
investigation_session_close(
application->session
);
if (application->main_window != NULL)
{
main_window_set_evidence_model(
application->main_window,
NULL
);
}
evidence_category_model_free(
application->evidence_category_model
);
evidence_list_model_free(
application->evidence_list_model
);
/*
* MainWindow contient TaskPanel.
* Il doit être détruit avant TaskManager.
*/
main_window_free(
application->main_window
);
/*
* ToolInitializer emprunte TaskManager.
* Il doit être libéré avant celui-ci.
*/
tool_initializer_free(
application->tool_initializer
);
task_manager_free(
application->task_manager
);
if (application->gtk_application != NULL)
{
g_object_unref(
application->gtk_application
);
}
g_clear_pointer(
&application->selected_evidence_identifier,
g_free
);
g_free(
application
);
}