feat(evidence): edit evidence classification
This commit is contained in:
parent
06fe3bcb21
commit
d876590529
15 changed files with 859 additions and 3 deletions
21
Makefile
21
Makefile
|
|
@ -120,6 +120,7 @@ TEST_OSINT_DNS_PROPOSAL := tests/test_osint_dns_proposal
|
|||
TEST_OSINT_DNS_INTEGRATION := tests/test_osint_dns_integration
|
||||
TEST_OSINT_EXECUTION_DAO := tests/test_osint_execution_dao
|
||||
TEST_OSINT_EXECUTION_INTEGRITY := tests/test_osint_execution_integrity
|
||||
TEST_EVIDENCE_RECLASSIFICATION := tests/test_evidence_reclassification
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
|
|
@ -595,6 +596,19 @@ $(TEST_OSINT_EXECUTION_INTEGRITY): \
|
|||
src/core/osint_execution_integrity.c
|
||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||
|
||||
$(TEST_EVIDENCE_RECLASSIFICATION): \
|
||||
tests/test_evidence_reclassification.c \
|
||||
src/core/evidence_reclassification.c \
|
||||
src/core/file_hash.c \
|
||||
src/dao/evidence_dao.c \
|
||||
src/models/evidence_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) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||
|
||||
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \
|
||||
tests/test_investigation_graph_load_task.c \
|
||||
src/core/investigation_graph_load_task.c \
|
||||
|
|
@ -669,7 +683,8 @@ test: \
|
|||
$(TEST_OSINT_DNS_PROPOSAL) \
|
||||
$(TEST_OSINT_DNS_INTEGRATION) \
|
||||
$(TEST_OSINT_EXECUTION_DAO) \
|
||||
$(TEST_OSINT_EXECUTION_INTEGRITY)
|
||||
$(TEST_OSINT_EXECUTION_INTEGRITY) \
|
||||
$(TEST_EVIDENCE_RECLASSIFICATION)
|
||||
@echo "Exécution des tests..."
|
||||
@./$(TEST_NODE)
|
||||
@./$(TEST_TREE_MODEL)
|
||||
|
|
@ -724,6 +739,7 @@ test: \
|
|||
@$(TEST_OSINT_DNS_INTEGRATION)
|
||||
@$(TEST_OSINT_EXECUTION_DAO)
|
||||
@$(TEST_OSINT_EXECUTION_INTEGRITY)
|
||||
@$(TEST_EVIDENCE_RECLASSIFICATION)
|
||||
@echo "Tous les tests sont valides."
|
||||
|
||||
%.o: %.c
|
||||
|
|
@ -784,7 +800,8 @@ clean:
|
|||
$(TEST_OSINT_DNS_PROPOSAL) \
|
||||
$(TEST_OSINT_DNS_INTEGRATION) \
|
||||
$(TEST_OSINT_EXECUTION_DAO) \
|
||||
$(TEST_OSINT_EXECUTION_INTEGRITY)
|
||||
$(TEST_OSINT_EXECUTION_INTEGRITY) \
|
||||
$(TEST_EVIDENCE_RECLASSIFICATION)
|
||||
|
||||
-include $(DEP)
|
||||
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ Le socle actuel comprend notamment :
|
|||
- panneau d’activité GTK ;
|
||||
- import groupé de preuves avec confirmation globale, révision individuelle,
|
||||
copie contrôlée, empreinte SHA-256 et bilan détaillé ;
|
||||
- correction du type, de la source et de la description d'une preuve avec
|
||||
contrôle d'intégrité et déplacement cohérent de sa copie interne ;
|
||||
- registre d’outils externes ;
|
||||
- exécution sécurisée par `GSubprocess` ;
|
||||
- exécution d’outils en tâche de fond ;
|
||||
|
|
|
|||
|
|
@ -766,6 +766,13 @@ importé séparément. Les imports sont exécutés séquentiellement afin d'évi
|
|||
les écritures SQLite concurrentes. L'échec d'un fichier n'annule pas les
|
||||
preuves déjà importées et apparaît dans le bilan final.
|
||||
|
||||
Le classement, la source et la description peuvent être corrigés après
|
||||
l'import. Un changement de type déplace la copie interne vers le dossier
|
||||
adapté dans la même opération logique que la mise à jour SQLite. L'empreinte
|
||||
et la taille sont vérifiées avant et après ; un échec annule la transaction et
|
||||
restaure le chemin initial. L'UUID, le nom interne et la date d'import restent
|
||||
inchangés.
|
||||
|
||||
---
|
||||
|
||||
## 4.6 entites
|
||||
|
|
|
|||
44
include/core/evidence_reclassification.h
Normal file
44
include/core/evidence_reclassification.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/******************************************************************************
|
||||
* @file evidence_reclassification.h
|
||||
* @brief Reclassement cohérent d'une preuve sans modifier son contenu.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef LABFY_INVESTIGATION_EVIDENCE_RECLASSIFICATION_H
|
||||
#define LABFY_INVESTIGATION_EVIDENCE_RECLASSIFICATION_H
|
||||
|
||||
#include "database/database.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* @brief Déplace une preuve et met à jour ses métadonnées avec retour arrière.
|
||||
*
|
||||
* L'intégrité est contrôlée avant et après le déplacement. Le contenu,
|
||||
* l'UUID, le nom interne, la taille, l'empreinte et la date d'import ne sont
|
||||
* jamais modifiés.
|
||||
*
|
||||
* @param database Connexion SQLite ouverte.
|
||||
* @param investigation_root Racine de l'enquête.
|
||||
* @param evidence_identifier UUID de la preuve.
|
||||
* @param type_identifier Nouveau type métier.
|
||||
* @param relative_directory Dossier relatif de destination.
|
||||
* @param source Nouvelle source, ou NULL.
|
||||
* @param description Nouvelle description, ou NULL.
|
||||
* @param error Adresse recevant une éventuelle erreur.
|
||||
* @return TRUE si le reclassement complet réussit.
|
||||
*/
|
||||
gboolean evidence_reclassification_apply(
|
||||
Database *database,
|
||||
const char *investigation_root,
|
||||
const char *evidence_identifier,
|
||||
const char *type_identifier,
|
||||
const char *relative_directory,
|
||||
const char *source,
|
||||
const char *description,
|
||||
GError **error
|
||||
);
|
||||
|
||||
G_END_DECLS
|
||||
#endif
|
||||
|
|
@ -144,6 +144,34 @@ gboolean evidence_dao_update_integrity_status(
|
|||
GError **error
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Met à jour le classement et les métadonnées éditables d'une preuve.
|
||||
*
|
||||
* L'UUID, le nom interne, la taille, l'empreinte et la date d'import restent
|
||||
* inchangés. La transaction éventuelle reste sous la responsabilité de
|
||||
* l'appelant.
|
||||
*
|
||||
* @param evidence_dao DAO valide.
|
||||
* @param identifier UUID de la preuve.
|
||||
* @param type_identifier Nouveau code de type.
|
||||
* @param relative_path Nouveau chemin relatif du fichier.
|
||||
* @param source Nouvelle source, ou NULL.
|
||||
* @param description Nouvelle description, ou NULL.
|
||||
* @param updated_at Date UTC de modification.
|
||||
* @param error Adresse recevant une éventuelle erreur.
|
||||
* @return TRUE si la mise à jour réussit.
|
||||
*/
|
||||
gboolean evidence_dao_update_metadata(
|
||||
EvidenceDao *evidence_dao,
|
||||
const char *identifier,
|
||||
const char *type_identifier,
|
||||
const char *relative_path,
|
||||
const char *source,
|
||||
const char *description,
|
||||
const char *updated_at,
|
||||
GError **error
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Compte les preuves persistées.
|
||||
*
|
||||
|
|
|
|||
53
include/views/evidence_metadata_dialog.h
Normal file
53
include/views/evidence_metadata_dialog.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/******************************************************************************
|
||||
* @file evidence_metadata_dialog.h
|
||||
* @brief Dialogue de modification des métadonnées éditables d'une preuve.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef LABFY_INVESTIGATION_EVIDENCE_METADATA_DIALOG_H
|
||||
#define LABFY_INVESTIGATION_EVIDENCE_METADATA_DIALOG_H
|
||||
|
||||
#include "models/evidence_record.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/** @brief Résultat opaque possédé du dialogue. */
|
||||
typedef struct EvidenceMetadataDialogResult EvidenceMetadataDialogResult;
|
||||
|
||||
/** @brief Callback recevant le résultat possédé, ou NULL après annulation. */
|
||||
typedef void (*EvidenceMetadataDialogCallback)(
|
||||
EvidenceMetadataDialogResult *result, gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Présente le dialogue prérempli de modification des métadonnées.
|
||||
* @param parent Fenêtre parente.
|
||||
* @param record Preuve empruntée.
|
||||
* @param evidence_types Tableau emprunté de EvidenceType.
|
||||
* @param callback Callback appelé une fois à la fermeture.
|
||||
* @param user_data Données empruntées du callback.
|
||||
* @return TRUE si la fenêtre a été créée.
|
||||
*/
|
||||
gboolean evidence_metadata_dialog_present(
|
||||
GtkWindow *parent,
|
||||
const EvidenceRecord *record,
|
||||
const GPtrArray *evidence_types,
|
||||
EvidenceMetadataDialogCallback callback,
|
||||
gpointer user_data
|
||||
);
|
||||
|
||||
/** @brief Libère un résultat, ou accepte NULL. */
|
||||
void evidence_metadata_dialog_result_free(EvidenceMetadataDialogResult *result);
|
||||
/** @brief Retourne le code de type emprunté. */
|
||||
const char *evidence_metadata_dialog_result_get_type_identifier(
|
||||
const EvidenceMetadataDialogResult *result);
|
||||
/** @brief Retourne la source empruntée, ou NULL. */
|
||||
const char *evidence_metadata_dialog_result_get_source(
|
||||
const EvidenceMetadataDialogResult *result);
|
||||
/** @brief Retourne la description empruntée, ou NULL. */
|
||||
const char *evidence_metadata_dialog_result_get_description(
|
||||
const EvidenceMetadataDialogResult *result);
|
||||
|
||||
G_END_DECLS
|
||||
#endif
|
||||
|
|
@ -86,6 +86,12 @@ typedef void (*MainWindowVerifyEvidenceCallback)(
|
|||
gpointer user_data
|
||||
);
|
||||
|
||||
/** @brief Callback appelé pour modifier les métadonnées d'une preuve. */
|
||||
typedef void (*MainWindowEditEvidenceCallback)(
|
||||
const char *evidence_identifier,
|
||||
gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Callback appelé après le déplacement effectif d'un nœud.
|
||||
*
|
||||
|
|
@ -151,6 +157,18 @@ void main_window_set_verify_evidence_callback(
|
|||
gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Définit le callback de modification de la preuve sélectionnée.
|
||||
* @param main_window Fenêtre principale.
|
||||
* @param callback Callback facultatif.
|
||||
* @param user_data Données privées transmises au callback.
|
||||
*/
|
||||
void main_window_set_edit_evidence_callback(
|
||||
MainWindow *main_window,
|
||||
MainWindowEditEvidenceCallback callback,
|
||||
gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Définit le callback de fin de déplacement d'un nœud.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -95,6 +95,12 @@ typedef void (*WorkspaceOsintActionCallback)(
|
|||
gpointer user_data
|
||||
);
|
||||
|
||||
/** @brief Callback appelé pour modifier les métadonnées d'une preuve. */
|
||||
typedef void (*WorkspaceEditEvidenceCallback)(
|
||||
const char *evidence_identifier,
|
||||
gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Crée une nouvelle zone de travail.
|
||||
*
|
||||
|
|
@ -204,6 +210,18 @@ void workspace_set_osint_action_callback(
|
|||
gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Définit le callback de modification de la preuve sélectionnée.
|
||||
* @param workspace Zone de travail.
|
||||
* @param callback Callback facultatif.
|
||||
* @param user_data Données empruntées transmises au callback.
|
||||
*/
|
||||
void workspace_set_edit_evidence_callback(
|
||||
Workspace *workspace,
|
||||
WorkspaceEditEvidenceCallback callback,
|
||||
gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Définit le callback de vérification de la preuve affichée.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -27,12 +27,14 @@
|
|||
#include "core/tool_task.h"
|
||||
#include "core/osint_dns_integration.h"
|
||||
#include "core/osint_execution_integrity.h"
|
||||
#include "core/evidence_reclassification.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/evidence_metadata_dialog.h"
|
||||
#include "views/create_relation_dialog.h"
|
||||
#include "views/osint_dns_review_dialog.h"
|
||||
#include "views/osint_execution_history_dialog.h"
|
||||
|
|
@ -159,6 +161,20 @@ typedef struct
|
|||
char *file_path;
|
||||
} ApplicationEvidenceDialogContext;
|
||||
|
||||
/** @brief Contexte possédé pendant l'édition d'une preuve. */
|
||||
typedef struct
|
||||
{
|
||||
Application *application;
|
||||
char *evidence_identifier;
|
||||
} ApplicationEvidenceMetadataContext;
|
||||
|
||||
/** @brief Données d'actualisation différée après reclassement. */
|
||||
typedef struct
|
||||
{
|
||||
Application *application;
|
||||
char *root_path;
|
||||
} ApplicationEvidenceRefreshContext;
|
||||
|
||||
/**
|
||||
* @brief Contexte conservé jusqu'à la fin d'une vérification.
|
||||
*/
|
||||
|
|
@ -3138,6 +3154,131 @@ static const char *application_get_evidence_subdirectory(
|
|||
return "Documents";
|
||||
}
|
||||
|
||||
/** @brief Libère le contexte d'édition des métadonnées. */
|
||||
static void application_evidence_metadata_context_free(
|
||||
ApplicationEvidenceMetadataContext *context
|
||||
)
|
||||
{
|
||||
if (context == NULL) return;
|
||||
g_free(context->evidence_identifier); g_free(context);
|
||||
}
|
||||
|
||||
/** @brief Actualise les vues après la destruction du dialogue d'édition. */
|
||||
static gboolean application_refresh_after_evidence_reclassification(
|
||||
gpointer user_data
|
||||
)
|
||||
{
|
||||
ApplicationEvidenceRefreshContext *context = user_data;
|
||||
GError *error = NULL;
|
||||
if (context != NULL && context->application != NULL &&
|
||||
context->application->main_window != NULL)
|
||||
{
|
||||
application_refresh_investigation_tree(
|
||||
context->application, context->root_path);
|
||||
application_refresh_evidence_models(context->application, &error);
|
||||
main_window_set_selected_evidence(
|
||||
context->application->main_window, NULL);
|
||||
application_message_dialog_present(
|
||||
main_window_get_window(context->application->main_window),
|
||||
APPLICATION_MESSAGE_DIALOG_INFORMATION, "Preuve reclassée",
|
||||
"Les métadonnées et le dossier ont été mis à jour. Le SHA-256 est inchangé.");
|
||||
}
|
||||
g_clear_error(&error);
|
||||
if (context != NULL) { g_free(context->root_path); g_free(context); }
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/** @brief Applique le reclassement confirmé puis actualise les vues. */
|
||||
static void application_on_evidence_metadata_edited(
|
||||
EvidenceMetadataDialogResult *result, gpointer user_data
|
||||
)
|
||||
{
|
||||
ApplicationEvidenceMetadataContext *context = user_data;
|
||||
Application *application = context != NULL ? context->application : NULL;
|
||||
const InvestigationProject *project = NULL;
|
||||
const char *root_path = NULL; const char *subdirectory = NULL;
|
||||
char *relative_directory = NULL; GError *error = NULL;
|
||||
if (result == NULL)
|
||||
{
|
||||
application_evidence_metadata_context_free(context);
|
||||
return;
|
||||
}
|
||||
if (application == NULL || application->session == NULL) goto cleanup;
|
||||
project = investigation_session_get_project(application->session);
|
||||
root_path = project != NULL ? investigation_project_get_root_path(project) : NULL;
|
||||
subdirectory = application_get_evidence_subdirectory(
|
||||
evidence_metadata_dialog_result_get_type_identifier(result));
|
||||
relative_directory = g_build_filename(
|
||||
APPLICATION_EVIDENCE_ROOT_DIRECTORY, subdirectory, NULL);
|
||||
if (!evidence_reclassification_apply(
|
||||
investigation_session_get_database(application->session), root_path,
|
||||
context->evidence_identifier,
|
||||
evidence_metadata_dialog_result_get_type_identifier(result),
|
||||
relative_directory,
|
||||
evidence_metadata_dialog_result_get_source(result),
|
||||
evidence_metadata_dialog_result_get_description(result), &error))
|
||||
{
|
||||
application_present_error(application, "Modification impossible",
|
||||
error != NULL ? error->message : "Le reclassement a échoué.");
|
||||
g_clear_error(&error); goto cleanup;
|
||||
}
|
||||
{
|
||||
ApplicationEvidenceRefreshContext *refresh_context =
|
||||
g_new0(ApplicationEvidenceRefreshContext, 1);
|
||||
refresh_context->application = application;
|
||||
refresh_context->root_path = g_strdup(root_path);
|
||||
g_timeout_add(50U,
|
||||
application_refresh_after_evidence_reclassification,
|
||||
refresh_context);
|
||||
}
|
||||
cleanup:
|
||||
g_free(relative_directory);
|
||||
evidence_metadata_dialog_result_free(result);
|
||||
application_evidence_metadata_context_free(context);
|
||||
}
|
||||
|
||||
/** @brief Charge et présente les métadonnées éditables d'une preuve. */
|
||||
static void application_on_edit_evidence_requested(
|
||||
const char *evidence_identifier, gpointer user_data
|
||||
)
|
||||
{
|
||||
Application *application = user_data; Database *database = NULL;
|
||||
EvidenceDao *evidence_dao = NULL; EvidenceTypeDao *type_dao = NULL;
|
||||
EvidenceRecord *record = NULL; GPtrArray *types = NULL;
|
||||
ApplicationEvidenceMetadataContext *context = NULL; GError *error = NULL;
|
||||
if (application == NULL || application->session == NULL ||
|
||||
evidence_identifier == NULL) return;
|
||||
database = investigation_session_get_database(application->session);
|
||||
evidence_dao = evidence_dao_new(database, &error);
|
||||
type_dao = evidence_type_dao_new(database, &error);
|
||||
if (evidence_dao != NULL)
|
||||
record = evidence_dao_find_by_identifier(
|
||||
evidence_dao, evidence_identifier, &error);
|
||||
if (type_dao != NULL) types = evidence_type_dao_list_all(type_dao, &error);
|
||||
if (record == NULL || types == NULL || types->len == 0U)
|
||||
{
|
||||
application_present_error(application, "Modification impossible",
|
||||
error != NULL ? error->message : "Les métadonnées sont indisponibles.");
|
||||
goto cleanup;
|
||||
}
|
||||
context = g_new0(ApplicationEvidenceMetadataContext, 1);
|
||||
context->application = application;
|
||||
context->evidence_identifier = g_strdup(evidence_identifier);
|
||||
if (!evidence_metadata_dialog_present(
|
||||
main_window_get_window(application->main_window), record, types,
|
||||
application_on_evidence_metadata_edited, context))
|
||||
{
|
||||
application_evidence_metadata_context_free(context);
|
||||
context = NULL;
|
||||
application_present_error(application, "Modification impossible",
|
||||
"Le dialogue de métadonnées n'a pas pu être créé.");
|
||||
}
|
||||
cleanup:
|
||||
g_clear_error(&error); g_clear_pointer(&types, g_ptr_array_unref);
|
||||
evidence_record_free(record); evidence_type_dao_free(type_dao);
|
||||
evidence_dao_free(evidence_dao);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prépare et démarre l’import asynchrone d’un fichier.
|
||||
*/
|
||||
|
|
@ -5952,6 +6093,12 @@ static void application_on_activate(
|
|||
application
|
||||
);
|
||||
|
||||
main_window_set_edit_evidence_callback(
|
||||
application->main_window,
|
||||
application_on_edit_evidence_requested,
|
||||
application
|
||||
);
|
||||
|
||||
main_window_set_graph_node_moved_callback(
|
||||
application->main_window,
|
||||
application_on_graph_node_moved,
|
||||
|
|
|
|||
120
src/core/evidence_reclassification.c
Normal file
120
src/core/evidence_reclassification.c
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/******************************************************************************
|
||||
* @file evidence_reclassification.c
|
||||
* @brief Reclassement cohérent d'une preuve sans modifier son contenu.
|
||||
******************************************************************************/
|
||||
|
||||
#include "core/evidence_reclassification.h"
|
||||
|
||||
#include "core/file_hash.h"
|
||||
#include "dao/evidence_dao.h"
|
||||
#include "database/transaction.h"
|
||||
#include "models/evidence_record.h"
|
||||
|
||||
#include <glib/gstdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
/** @brief Enregistre une erreur de reclassement si aucune n'existe encore. */
|
||||
static void evidence_reclassification_set_error(
|
||||
GError **error, const char *message
|
||||
)
|
||||
{
|
||||
if (error != NULL && *error == NULL)
|
||||
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, message);
|
||||
}
|
||||
|
||||
gboolean evidence_reclassification_apply(
|
||||
Database *database, const char *investigation_root,
|
||||
const char *evidence_identifier, const char *type_identifier,
|
||||
const char *relative_directory, const char *source,
|
||||
const char *description, GError **error
|
||||
)
|
||||
{
|
||||
EvidenceDao *dao = NULL; EvidenceRecord *record = NULL;
|
||||
char *old_path = NULL; char *new_path = NULL; char *new_relative_path = NULL;
|
||||
char *new_parent = NULL; char *sha256 = NULL; char *updated_at = NULL;
|
||||
guint64 size_bytes = 0U; GDateTime *now = NULL;
|
||||
gboolean moved = FALSE; gboolean transaction_started = FALSE;
|
||||
gboolean success = FALSE;
|
||||
if (database == NULL || investigation_root == NULL ||
|
||||
evidence_identifier == NULL || type_identifier == NULL ||
|
||||
relative_directory == NULL || g_path_is_absolute(relative_directory) ||
|
||||
strstr(relative_directory, "..") != NULL)
|
||||
{
|
||||
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||
"Les paramètres de reclassement sont invalides.");
|
||||
return FALSE;
|
||||
}
|
||||
dao = evidence_dao_new(database, error);
|
||||
if (dao == NULL) goto cleanup;
|
||||
record = evidence_dao_find_by_identifier(dao, evidence_identifier, error);
|
||||
if (record == NULL) goto cleanup;
|
||||
old_path = g_build_filename(investigation_root,
|
||||
evidence_record_get_relative_path(record), NULL);
|
||||
new_relative_path = g_build_filename(relative_directory,
|
||||
evidence_record_get_internal_name(record), NULL);
|
||||
new_path = g_build_filename(investigation_root, new_relative_path, NULL);
|
||||
new_parent = g_path_get_dirname(new_path);
|
||||
if (!file_hash_compute_sha256(old_path, NULL, &sha256, &size_bytes, error) ||
|
||||
g_strcmp0(sha256, evidence_record_get_sha256(record)) != 0 ||
|
||||
size_bytes != evidence_record_get_size_bytes(record))
|
||||
{
|
||||
evidence_reclassification_set_error(error,
|
||||
"L'intégrité de la preuve source n'est pas conforme.");
|
||||
goto cleanup;
|
||||
}
|
||||
g_clear_pointer(&sha256, g_free);
|
||||
if (g_mkdir_with_parents(new_parent, 0700) != 0)
|
||||
{
|
||||
g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errno),
|
||||
"Impossible de préparer le dossier de destination : %s",
|
||||
g_strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
if (!database_transaction_begin(database))
|
||||
{
|
||||
evidence_reclassification_set_error(error,
|
||||
"Impossible de démarrer la transaction de reclassement.");
|
||||
goto cleanup;
|
||||
}
|
||||
transaction_started = TRUE;
|
||||
if (g_strcmp0(old_path, new_path) != 0)
|
||||
{
|
||||
if (g_file_test(new_path, G_FILE_TEST_EXISTS) ||
|
||||
g_rename(old_path, new_path) != 0)
|
||||
{
|
||||
g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errno),
|
||||
"Impossible de déplacer la preuve : %s", g_strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
moved = TRUE;
|
||||
}
|
||||
now = g_date_time_new_now_utc();
|
||||
updated_at = now != NULL
|
||||
? g_date_time_format(now, "%Y-%m-%dT%H:%M:%SZ") : NULL;
|
||||
if (!evidence_dao_update_metadata(dao, evidence_identifier,
|
||||
type_identifier, new_relative_path, source, description,
|
||||
updated_at, error) ||
|
||||
!file_hash_compute_sha256(new_path, NULL, &sha256, &size_bytes, error) ||
|
||||
g_strcmp0(sha256, evidence_record_get_sha256(record)) != 0 ||
|
||||
size_bytes != evidence_record_get_size_bytes(record))
|
||||
{
|
||||
evidence_reclassification_set_error(error,
|
||||
"L'intégrité après reclassement n'est pas conforme.");
|
||||
goto cleanup;
|
||||
}
|
||||
if (!database_transaction_commit(database))
|
||||
{
|
||||
evidence_reclassification_set_error(error,
|
||||
"Impossible de valider le reclassement.");
|
||||
goto cleanup;
|
||||
}
|
||||
transaction_started = FALSE; success = TRUE;
|
||||
cleanup:
|
||||
if (!success && transaction_started) database_transaction_rollback(database);
|
||||
if (!success && moved && g_rename(new_path, old_path) != 0)
|
||||
g_warning("Impossible de restaurer la preuve '%s'.", old_path);
|
||||
g_clear_pointer(&now, g_date_time_unref); g_free(updated_at); g_free(sha256);
|
||||
g_free(new_parent); g_free(new_path); g_free(new_relative_path); g_free(old_path);
|
||||
evidence_record_free(record); evidence_dao_free(dao);
|
||||
return success;
|
||||
}
|
||||
|
|
@ -156,6 +156,11 @@ static const char *const evidence_dao_update_integrity_status_sql =
|
|||
"SET integrity_status = ? "
|
||||
"WHERE id = ?;";
|
||||
|
||||
/** @brief Requête de mise à jour des métadonnées éditables. */
|
||||
static const char *const evidence_dao_update_metadata_sql =
|
||||
"UPDATE preuves SET type_id=(SELECT id FROM types_preuve WHERE code=?),"
|
||||
"relative_path=?,source=?,description=?,updated_at=? WHERE id=?;";
|
||||
|
||||
/**
|
||||
* @brief Enregistre une erreur littérale.
|
||||
*/
|
||||
|
|
@ -1344,6 +1349,49 @@ cleanup:
|
|||
return success;
|
||||
}
|
||||
|
||||
gboolean evidence_dao_update_metadata(
|
||||
EvidenceDao *evidence_dao, const char *identifier,
|
||||
const char *type_identifier, const char *relative_path,
|
||||
const char *source, const char *description, const char *updated_at,
|
||||
GError **error
|
||||
)
|
||||
{
|
||||
DatabaseStatement *statement = NULL;
|
||||
gboolean success = FALSE;
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
if (evidence_dao == NULL || evidence_dao->database == NULL ||
|
||||
identifier == NULL || !g_uuid_string_is_valid(identifier) ||
|
||||
type_identifier == NULL || type_identifier[0] == '\0' ||
|
||||
relative_path == NULL || relative_path[0] == '\0' ||
|
||||
g_path_is_absolute(relative_path) || updated_at == NULL ||
|
||||
strlen(updated_at) != 20U)
|
||||
{
|
||||
evidence_dao_set_error_literal(error,
|
||||
EVIDENCE_DAO_ERROR_INVALID_ARGUMENT,
|
||||
"Les métadonnées de reclassement sont invalides.");
|
||||
return FALSE;
|
||||
}
|
||||
statement = database_statement_prepare(
|
||||
evidence_dao->database, evidence_dao_update_metadata_sql);
|
||||
if (statement == NULL) goto cleanup;
|
||||
success = database_statement_bind_text(statement, 1, type_identifier) &&
|
||||
database_statement_bind_text(statement, 2, relative_path) &&
|
||||
(source != NULL ? database_statement_bind_text(statement, 3, source)
|
||||
: database_statement_bind_null(statement, 3)) &&
|
||||
(description != NULL
|
||||
? database_statement_bind_text(statement, 4, description)
|
||||
: database_statement_bind_null(statement, 4)) &&
|
||||
database_statement_bind_text(statement, 5, updated_at) &&
|
||||
database_statement_bind_text(statement, 6, identifier) &&
|
||||
database_statement_step(statement) == DATABASE_STATEMENT_STEP_DONE;
|
||||
cleanup:
|
||||
if (!success) evidence_dao_set_database_error(evidence_dao, error,
|
||||
EVIDENCE_DAO_ERROR_EXECUTE,
|
||||
"Impossible de mettre à jour les métadonnées de la preuve");
|
||||
database_statement_finalize(statement);
|
||||
return success;
|
||||
}
|
||||
|
||||
gboolean evidence_dao_count(
|
||||
EvidenceDao *evidence_dao,
|
||||
guint64 *out_count,
|
||||
|
|
|
|||
212
src/views/evidence_metadata_dialog.c
Normal file
212
src/views/evidence_metadata_dialog.c
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/******************************************************************************
|
||||
* @file evidence_metadata_dialog.c
|
||||
* @brief Dialogue de modification des métadonnées éditables d'une preuve.
|
||||
******************************************************************************/
|
||||
|
||||
#include "views/evidence_metadata_dialog.h"
|
||||
|
||||
#include "models/evidence_type.h"
|
||||
|
||||
struct EvidenceMetadataDialogResult
|
||||
{
|
||||
char *type_identifier;
|
||||
char *source;
|
||||
char *description;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkWindow *window;
|
||||
GtkDropDown *type_dropdown;
|
||||
GtkEditable *source_entry;
|
||||
GtkTextBuffer *description_buffer;
|
||||
GtkStringList *type_labels;
|
||||
GPtrArray *type_identifiers;
|
||||
EvidenceMetadataDialogCallback callback;
|
||||
gpointer user_data;
|
||||
gboolean completed;
|
||||
} EvidenceMetadataDialogContext;
|
||||
|
||||
/** @brief Copie un texte facultatif et transforme une chaîne vide en NULL. */
|
||||
static char *evidence_metadata_dialog_copy_optional(const char *text)
|
||||
{
|
||||
char *copy = text != NULL ? g_strdup(text) : NULL;
|
||||
if (copy != NULL) g_strstrip(copy);
|
||||
if (copy != NULL && copy[0] == '\0') g_clear_pointer(©, g_free);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** @brief Libère le contexte détenu par la fenêtre. */
|
||||
static void evidence_metadata_dialog_context_free(gpointer data)
|
||||
{
|
||||
EvidenceMetadataDialogContext *context = data;
|
||||
if (context == NULL) return;
|
||||
g_clear_pointer(&context->type_identifiers, g_ptr_array_unref);
|
||||
g_clear_object(&context->type_labels);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/** @brief Détruit la fenêtre hors de la pile du signal GTK courant. */
|
||||
static gboolean evidence_metadata_dialog_destroy_idle(gpointer data)
|
||||
{
|
||||
GtkWindow *window = GTK_WINDOW(data);
|
||||
gtk_window_destroy(window);
|
||||
g_object_unref(window);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/** @brief Termine le dialogue et transmet éventuellement un résultat. */
|
||||
static void evidence_metadata_dialog_complete(
|
||||
EvidenceMetadataDialogContext *context,
|
||||
EvidenceMetadataDialogResult *result
|
||||
)
|
||||
{
|
||||
if (context == NULL || context->completed) return;
|
||||
context->completed = TRUE;
|
||||
if (context->callback != NULL) context->callback(result, context->user_data);
|
||||
else evidence_metadata_dialog_result_free(result);
|
||||
gtk_widget_set_visible(GTK_WIDGET(context->window), FALSE);
|
||||
g_idle_add(
|
||||
evidence_metadata_dialog_destroy_idle,
|
||||
g_object_ref(context->window)
|
||||
);
|
||||
}
|
||||
|
||||
/** @brief Annule la modification. */
|
||||
static void evidence_metadata_dialog_on_cancel(
|
||||
GtkButton *button, gpointer user_data
|
||||
)
|
||||
{
|
||||
(void) button;
|
||||
evidence_metadata_dialog_complete(user_data, NULL);
|
||||
}
|
||||
|
||||
/** @brief Traite la fermeture native comme une annulation explicite. */
|
||||
static gboolean evidence_metadata_dialog_on_close_request(
|
||||
GtkWindow *window, gpointer user_data
|
||||
)
|
||||
{
|
||||
(void) window;
|
||||
evidence_metadata_dialog_complete(user_data, NULL);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/** @brief Construit et transmet les valeurs modifiées. */
|
||||
static void evidence_metadata_dialog_on_save(
|
||||
GtkButton *button, gpointer user_data
|
||||
)
|
||||
{
|
||||
EvidenceMetadataDialogContext *context = user_data;
|
||||
EvidenceMetadataDialogResult *result = NULL;
|
||||
GtkTextIter start; GtkTextIter end; char *description = NULL;
|
||||
guint selected = 0U;
|
||||
(void) button;
|
||||
if (context == NULL) return;
|
||||
selected = gtk_drop_down_get_selected(context->type_dropdown);
|
||||
if (selected >= context->type_identifiers->len) return;
|
||||
gtk_text_buffer_get_bounds(context->description_buffer, &start, &end);
|
||||
description = gtk_text_buffer_get_text(
|
||||
context->description_buffer, &start, &end, FALSE);
|
||||
result = g_new0(EvidenceMetadataDialogResult, 1);
|
||||
result->type_identifier = g_strdup(
|
||||
g_ptr_array_index(context->type_identifiers, selected));
|
||||
result->source = evidence_metadata_dialog_copy_optional(
|
||||
gtk_editable_get_text(context->source_entry));
|
||||
result->description = evidence_metadata_dialog_copy_optional(description);
|
||||
g_free(description);
|
||||
evidence_metadata_dialog_complete(context, result);
|
||||
}
|
||||
|
||||
gboolean evidence_metadata_dialog_present(
|
||||
GtkWindow *parent, const EvidenceRecord *record,
|
||||
const GPtrArray *evidence_types, EvidenceMetadataDialogCallback callback,
|
||||
gpointer user_data
|
||||
)
|
||||
{
|
||||
EvidenceMetadataDialogContext *context = NULL;
|
||||
GtkStringList *labels = NULL; GtkWidget *main_box = NULL;
|
||||
GtkWidget *description_view = NULL; GtkWidget *description_scroll = NULL;
|
||||
GtkWidget *buttons = NULL; GtkWidget *cancel = NULL; GtkWidget *save = NULL;
|
||||
guint selected = 0U;
|
||||
if (record == NULL || evidence_types == NULL || evidence_types->len == 0U ||
|
||||
callback == NULL) return FALSE;
|
||||
context = g_new0(EvidenceMetadataDialogContext, 1);
|
||||
context->window = GTK_WINDOW(gtk_window_new());
|
||||
context->callback = callback; context->user_data = user_data;
|
||||
context->type_identifiers = g_ptr_array_new_with_free_func(g_free);
|
||||
labels = gtk_string_list_new(NULL);
|
||||
context->type_labels = labels;
|
||||
for (guint index = 0U; index < evidence_types->len; index++)
|
||||
{
|
||||
const EvidenceType *type = g_ptr_array_index((GPtrArray *) evidence_types, index);
|
||||
gtk_string_list_append(labels, evidence_type_get_label(type));
|
||||
g_ptr_array_add(context->type_identifiers,
|
||||
g_strdup(evidence_type_get_code(type)));
|
||||
if (g_strcmp0(evidence_type_get_code(type),
|
||||
evidence_record_get_type_identifier(record)) == 0) selected = index;
|
||||
}
|
||||
gtk_window_set_title(context->window, "Modifier les métadonnées");
|
||||
gtk_window_set_default_size(context->window, 560, 420);
|
||||
gtk_window_set_modal(context->window, TRUE);
|
||||
gtk_window_set_destroy_with_parent(context->window, TRUE);
|
||||
if (parent != NULL) gtk_window_set_transient_for(context->window, parent);
|
||||
g_object_set_data_full(G_OBJECT(context->window), "evidence-metadata-context",
|
||||
context, evidence_metadata_dialog_context_free);
|
||||
main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
||||
gtk_widget_set_margin_start(main_box, 18); gtk_widget_set_margin_end(main_box, 18);
|
||||
gtk_widget_set_margin_top(main_box, 18); gtk_widget_set_margin_bottom(main_box, 18);
|
||||
gtk_box_append(GTK_BOX(main_box), gtk_label_new(
|
||||
"Le fichier, son UUID, sa taille, sa date d'import et son SHA-256 resteront inchangés."));
|
||||
context->type_dropdown = GTK_DROP_DOWN(gtk_drop_down_new(
|
||||
G_LIST_MODEL(labels), NULL));
|
||||
gtk_drop_down_set_selected(context->type_dropdown, selected);
|
||||
gtk_box_append(GTK_BOX(main_box), gtk_label_new("Type de preuve"));
|
||||
gtk_box_append(GTK_BOX(main_box), GTK_WIDGET(context->type_dropdown));
|
||||
context->source_entry = GTK_EDITABLE(gtk_entry_new());
|
||||
gtk_editable_set_text(context->source_entry,
|
||||
evidence_record_get_source(record) != NULL
|
||||
? evidence_record_get_source(record) : "");
|
||||
gtk_box_append(GTK_BOX(main_box), gtk_label_new("Source"));
|
||||
gtk_box_append(GTK_BOX(main_box), GTK_WIDGET(context->source_entry));
|
||||
description_view = gtk_text_view_new();
|
||||
context->description_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(description_view));
|
||||
gtk_text_buffer_set_text(context->description_buffer,
|
||||
evidence_record_get_description(record) != NULL
|
||||
? evidence_record_get_description(record) : "", -1);
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(description_view), GTK_WRAP_WORD_CHAR);
|
||||
description_scroll = gtk_scrolled_window_new();
|
||||
gtk_widget_set_vexpand(description_scroll, TRUE);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(description_scroll), description_view);
|
||||
gtk_box_append(GTK_BOX(main_box), gtk_label_new("Description"));
|
||||
gtk_box_append(GTK_BOX(main_box), description_scroll);
|
||||
buttons = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_widget_set_halign(buttons, GTK_ALIGN_END);
|
||||
cancel = gtk_button_new_with_label("Annuler");
|
||||
save = gtk_button_new_with_label("Enregistrer");
|
||||
gtk_widget_add_css_class(save, "suggested-action");
|
||||
g_signal_connect(cancel, "clicked", G_CALLBACK(evidence_metadata_dialog_on_cancel), context);
|
||||
g_signal_connect(save, "clicked", G_CALLBACK(evidence_metadata_dialog_on_save), context);
|
||||
g_signal_connect(context->window, "close-request",
|
||||
G_CALLBACK(evidence_metadata_dialog_on_close_request), context);
|
||||
gtk_box_append(GTK_BOX(buttons), cancel); gtk_box_append(GTK_BOX(buttons), save);
|
||||
gtk_box_append(GTK_BOX(main_box), buttons);
|
||||
gtk_window_set_child(context->window, main_box);
|
||||
gtk_window_present(context->window);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void evidence_metadata_dialog_result_free(EvidenceMetadataDialogResult *result)
|
||||
{
|
||||
if (result == NULL) return;
|
||||
g_free(result->type_identifier); g_free(result->source);
|
||||
g_free(result->description); g_free(result);
|
||||
}
|
||||
const char *evidence_metadata_dialog_result_get_type_identifier(
|
||||
const EvidenceMetadataDialogResult *result)
|
||||
{ return result != NULL ? result->type_identifier : NULL; }
|
||||
const char *evidence_metadata_dialog_result_get_source(
|
||||
const EvidenceMetadataDialogResult *result)
|
||||
{ return result != NULL ? result->source : NULL; }
|
||||
const char *evidence_metadata_dialog_result_get_description(
|
||||
const EvidenceMetadataDialogResult *result)
|
||||
{ return result != NULL ? result->description : NULL; }
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
/**
|
||||
* @brief Position initiale de la séparation horizontale.
|
||||
*/
|
||||
#define MAIN_WINDOW_SIDEBAR_POSITION 320
|
||||
#define MAIN_WINDOW_SIDEBAR_POSITION 640
|
||||
|
||||
/**
|
||||
* @brief Position initiale de la séparation verticale.
|
||||
|
|
@ -99,6 +99,9 @@ struct MainWindow
|
|||
gpointer
|
||||
verify_evidence_user_data;
|
||||
|
||||
MainWindowEditEvidenceCallback edit_evidence_callback;
|
||||
gpointer edit_evidence_user_data;
|
||||
|
||||
MainWindowGraphNodeMovedCallback
|
||||
graph_node_moved_callback;
|
||||
|
||||
|
|
@ -156,6 +159,17 @@ static void main_window_on_osint_action_requested(
|
|||
);
|
||||
}
|
||||
|
||||
/** @brief Relaie la modification d'une preuve vers l'application. */
|
||||
static void main_window_on_edit_evidence_requested(
|
||||
const char *evidence_identifier, gpointer user_data
|
||||
)
|
||||
{
|
||||
MainWindow *main_window = user_data;
|
||||
if (main_window == NULL || main_window->edit_evidence_callback == NULL) return;
|
||||
main_window->edit_evidence_callback(
|
||||
evidence_identifier, main_window->edit_evidence_user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ouvre dans le workspace l'entité choisie dans la sidebar.
|
||||
*/
|
||||
|
|
@ -782,6 +796,12 @@ MainWindow *main_window_new(
|
|||
main_window
|
||||
);
|
||||
|
||||
workspace_set_edit_evidence_callback(
|
||||
main_window->workspace,
|
||||
main_window_on_edit_evidence_requested,
|
||||
main_window
|
||||
);
|
||||
|
||||
workspace_widget = workspace_get_widget(
|
||||
main_window->workspace
|
||||
);
|
||||
|
|
@ -1297,6 +1317,16 @@ void main_window_set_osint_action_callback(
|
|||
main_window->osint_action_user_data = user_data;
|
||||
}
|
||||
|
||||
void main_window_set_edit_evidence_callback(
|
||||
MainWindow *main_window, MainWindowEditEvidenceCallback callback,
|
||||
gpointer user_data
|
||||
)
|
||||
{
|
||||
if (main_window == NULL) return;
|
||||
main_window->edit_evidence_callback = callback;
|
||||
main_window->edit_evidence_user_data = user_data;
|
||||
}
|
||||
|
||||
void main_window_set_tree_selection_callback(
|
||||
MainWindow *main_window,
|
||||
InvestigationTreeViewSelectionCallback callback,
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ struct Workspace
|
|||
GtkWidget *evidence_identifier_label;
|
||||
GtkWidget *evidence_sha256_label;
|
||||
GtkWidget *verify_evidence_button;
|
||||
GtkWidget *edit_evidence_button;
|
||||
|
||||
char *selected_evidence_identifier;
|
||||
|
||||
|
|
@ -94,6 +95,9 @@ struct Workspace
|
|||
gpointer
|
||||
verify_evidence_user_data;
|
||||
|
||||
WorkspaceEditEvidenceCallback edit_evidence_callback;
|
||||
gpointer edit_evidence_user_data;
|
||||
|
||||
WorkspaceGraphNodeMovedCallback
|
||||
graph_node_moved_callback;
|
||||
|
||||
|
|
@ -834,6 +838,20 @@ static void workspace_on_verify_evidence_clicked(
|
|||
);
|
||||
}
|
||||
|
||||
/** @brief Transmet la demande de modification de la preuve affichée. */
|
||||
static void workspace_on_edit_evidence_clicked(
|
||||
GtkButton *button, gpointer user_data
|
||||
)
|
||||
{
|
||||
Workspace *workspace = user_data;
|
||||
(void) button;
|
||||
if (workspace == NULL || workspace->edit_evidence_callback == NULL ||
|
||||
workspace->selected_evidence_identifier == NULL) return;
|
||||
workspace->edit_evidence_callback(
|
||||
workspace->selected_evidence_identifier,
|
||||
workspace->edit_evidence_user_data);
|
||||
}
|
||||
|
||||
Workspace *workspace_new(void)
|
||||
{
|
||||
GtkWidget *evidence_content = NULL;
|
||||
|
|
@ -1153,6 +1171,16 @@ Workspace *workspace_new(void)
|
|||
workspace->verify_evidence_button
|
||||
);
|
||||
|
||||
workspace->edit_evidence_button = gtk_button_new_with_label(
|
||||
"Modifier les métadonnées");
|
||||
gtk_widget_set_halign(workspace->edit_evidence_button, GTK_ALIGN_START);
|
||||
gtk_widget_set_sensitive(workspace->edit_evidence_button, FALSE);
|
||||
gtk_widget_set_tooltip_text(workspace->edit_evidence_button,
|
||||
"Corriger le classement sans modifier le fichier original");
|
||||
g_signal_connect(workspace->edit_evidence_button, "clicked",
|
||||
G_CALLBACK(workspace_on_edit_evidence_clicked), workspace);
|
||||
gtk_box_append(GTK_BOX(evidence_content), workspace->edit_evidence_button);
|
||||
|
||||
evidence_separator =
|
||||
gtk_separator_new(
|
||||
GTK_ORIENTATION_HORIZONTAL
|
||||
|
|
@ -2079,6 +2107,8 @@ void workspace_set_selected_node(
|
|||
FALSE
|
||||
);
|
||||
}
|
||||
if (workspace->edit_evidence_button != NULL)
|
||||
gtk_widget_set_sensitive(workspace->edit_evidence_button, FALSE);
|
||||
|
||||
if (node == NULL)
|
||||
{
|
||||
|
|
@ -2233,6 +2263,7 @@ void workspace_set_selected_evidence(
|
|||
workspace->verify_evidence_button,
|
||||
TRUE
|
||||
);
|
||||
gtk_widget_set_sensitive(workspace->edit_evidence_button, TRUE);
|
||||
|
||||
size_bytes =
|
||||
evidence_record_get_size_bytes(
|
||||
|
|
@ -2774,6 +2805,16 @@ void workspace_set_verify_evidence_callback(
|
|||
user_data;
|
||||
}
|
||||
|
||||
void workspace_set_edit_evidence_callback(
|
||||
Workspace *workspace, WorkspaceEditEvidenceCallback callback,
|
||||
gpointer user_data
|
||||
)
|
||||
{
|
||||
if (workspace == NULL) return;
|
||||
workspace->edit_evidence_callback = callback;
|
||||
workspace->edit_evidence_user_data = user_data;
|
||||
}
|
||||
|
||||
void workspace_set_graph_node_moved_callback(
|
||||
Workspace *workspace,
|
||||
WorkspaceGraphNodeMovedCallback callback,
|
||||
|
|
|
|||
71
tests/test_evidence_reclassification.c
Normal file
71
tests/test_evidence_reclassification.c
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/******************************************************************************
|
||||
* @file test_evidence_reclassification.c
|
||||
* @brief Tests du reclassement cohérent des preuves.
|
||||
******************************************************************************/
|
||||
|
||||
#include "core/evidence_reclassification.h"
|
||||
#include "core/file_hash.h"
|
||||
#include "dao/evidence_dao.h"
|
||||
#include "database/database.h"
|
||||
#include "models/evidence_record.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
static void test_reclassification_preserves_identity_and_hash(void)
|
||||
{
|
||||
GError *error = NULL; guint64 size = 0U; char *sha256 = NULL;
|
||||
char *root = g_dir_make_tmp("labfy-reclassify-XXXXXX", &error);
|
||||
char *database_dir = g_build_filename(root, "00_BaseDeDonnees", NULL);
|
||||
char *database_path = g_build_filename(database_dir, "Enquete.sqlite", NULL);
|
||||
char *old_dir = g_build_filename(root, "01_Preuves_Originales", "Documents", NULL);
|
||||
char *old_path = g_build_filename(old_dir, "internal.bin", NULL);
|
||||
char *new_path = g_build_filename(root, "01_Preuves_Originales", "Photos",
|
||||
"internal.bin", NULL);
|
||||
Database *database = NULL; EvidenceDao *dao = NULL;
|
||||
EvidenceRecord *record = NULL; EvidenceRecord *loaded = NULL;
|
||||
g_assert_no_error(error);
|
||||
g_assert_cmpint(g_mkdir_with_parents(database_dir, 0700), ==, 0);
|
||||
g_assert_cmpint(g_mkdir_with_parents(old_dir, 0700), ==, 0);
|
||||
g_assert_true(g_file_set_contents(old_path, "binary\0content", 14, &error));
|
||||
g_assert_true(file_hash_compute_sha256(old_path, NULL, &sha256, &size, &error));
|
||||
g_assert_true(database_initialize(database_path, "Test", root));
|
||||
database = database_open(database_path); dao = evidence_dao_new(database, &error);
|
||||
record = evidence_record_new(
|
||||
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "original.bin", "internal.bin",
|
||||
"01_Preuves_Originales/Documents/internal.bin", "document", size, sha256,
|
||||
"2026-07-22T10:00:00Z", "2026-07-18T10:00:00Z", "Victime", "Avant",
|
||||
EVIDENCE_INTEGRITY_STATUS_UNKNOWN, &error);
|
||||
g_assert_nonnull(record); g_assert_true(evidence_dao_insert(dao, record, &error));
|
||||
g_assert_true(evidence_reclassification_apply(
|
||||
database, root, "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "photo",
|
||||
"01_Preuves_Originales/Photos", "Victime", "Après", &error));
|
||||
g_assert_false(g_file_test(old_path, G_FILE_TEST_EXISTS));
|
||||
g_assert_true(g_file_test(new_path, G_FILE_TEST_IS_REGULAR));
|
||||
loaded = evidence_dao_find_by_identifier(
|
||||
dao, "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", &error);
|
||||
g_assert_cmpstr(evidence_record_get_type_identifier(loaded), ==, "photo");
|
||||
g_assert_cmpstr(evidence_record_get_sha256(loaded), ==, sha256);
|
||||
g_assert_cmpstr(evidence_record_get_imported_at(loaded), ==,
|
||||
"2026-07-22T10:00:00Z");
|
||||
g_assert_cmpstr(evidence_record_get_description(loaded), ==, "Après");
|
||||
evidence_record_free(loaded); evidence_record_free(record);
|
||||
evidence_dao_free(dao); database_close(database);
|
||||
g_assert_cmpint(g_remove(new_path), ==, 0);
|
||||
g_assert_cmpint(g_remove(database_path), ==, 0);
|
||||
char *photos = g_path_get_dirname(new_path);
|
||||
char *originals = g_path_get_dirname(photos);
|
||||
g_assert_cmpint(g_rmdir(old_dir), ==, 0); g_assert_cmpint(g_rmdir(photos), ==, 0);
|
||||
g_assert_cmpint(g_rmdir(originals), ==, 0); g_assert_cmpint(g_rmdir(database_dir), ==, 0);
|
||||
g_assert_cmpint(g_rmdir(root), ==, 0);
|
||||
g_free(photos); g_free(originals); g_free(sha256); g_free(new_path);
|
||||
g_free(old_path); g_free(old_dir); g_free(database_path); g_free(database_dir); g_free(root);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
g_test_add_func("/evidence-reclassification/preserve",
|
||||
test_reclassification_preserves_identity_and_hash);
|
||||
return g_test_run();
|
||||
}
|
||||
Loading…
Reference in a new issue