labfy-investigation/src/core/investigation_graph_load_task.c
2026-07-21 23:27:07 +02:00

1854 lines
38 KiB
C

/******************************************************************************
* @file investigation_graph_load_task.c
* @brief Chargement asynchrone du graphe métier d'une enquête.
******************************************************************************/
#include "core/investigation_graph_load_task.h"
#include "core/background_task.h"
#include "core/investigation_graph_loader.h"
#include "dao/graph_node_position_dao.h"
#include "database/database.h"
#include "models/graph_node_position.h"
#include "models/investigation_graph_layout.h"
#include <gio/gio.h>
#include <glib.h>
#ifdef INVESTIGATION_GRAPH_LOAD_TASK_ENABLE_TEST_HOOKS
#include "core/investigation_graph_load_task_test.h"
#endif
/**
* @brief Données privées copiées pour une exécution du worker.
*/
typedef struct
{
char *database_path;
} InvestigationGraphLoadWorkerData;
/**
* @brief Résultat possédé par BackgroundTask jusqu'au callback final.
*
* Le callback retire graph_model de ce conteneur avant de le transmettre à
* l'appelant. Le destructeur du conteneur ne détruit donc que les graphes qui
* n'ont pas été remis.
*/
typedef struct
{
InvestigationGraphModel *graph_model;
InvestigationGraphLayout *graph_layout;
} InvestigationGraphLoadResult;
/**
* @brief Contexte maintenant la tâche publique en vie jusqu'à la fin.
*/
typedef struct
{
InvestigationGraphLoadTask *load_task;
} InvestigationGraphLoadCompletionContext;
/**
* @brief État privé de la tâche de chargement.
*/
struct InvestigationGraphLoadTask
{
gatomicrefcount reference_count;
GMutex mutex;
char *database_path;
BackgroundTask *background_task;
InvestigationGraphLoadTaskCallback callback;
gpointer user_data;
GDestroyNotify user_data_destroy;
InvestigationGraphLayout *completed_layout;
gboolean running;
gboolean disposed;
};
#ifdef INVESTIGATION_GRAPH_LOAD_TASK_ENABLE_TEST_HOOKS
/**
* @brief État global des synchronisations déterministes réservées aux tests.
*/
typedef struct
{
GMutex mutex;
GCond condition;
gboolean pause_before_open;
gboolean reached_before_open;
gboolean release_before_open;
gboolean pause_after_open;
gboolean reached_after_open;
gboolean release_after_open;
guint completion_count;
} InvestigationGraphLoadTaskTestHooks;
static InvestigationGraphLoadTaskTestHooks
investigation_graph_load_task_test_hooks;
static gsize investigation_graph_load_task_test_hooks_initialized =
0;
/**
* @brief Initialise une seule fois les primitives des hooks de test.
*/
static void investigation_graph_load_task_test_ensure_initialized(void)
{
if (g_once_init_enter(
&investigation_graph_load_task_test_hooks_initialized
))
{
g_mutex_init(
&investigation_graph_load_task_test_hooks.mutex
);
g_cond_init(
&investigation_graph_load_task_test_hooks.condition
);
g_once_init_leave(
&investigation_graph_load_task_test_hooks_initialized,
1
);
}
}
/**
* @brief Suspend éventuellement le worker avant l'ouverture SQLite.
*/
static void investigation_graph_load_task_test_wait_before_open_hook(void)
{
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
investigation_graph_load_task_test_hooks.reached_before_open =
TRUE;
g_cond_broadcast(
&investigation_graph_load_task_test_hooks.condition
);
while (investigation_graph_load_task_test_hooks.pause_before_open &&
!investigation_graph_load_task_test_hooks.release_before_open)
{
g_cond_wait(
&investigation_graph_load_task_test_hooks.condition,
&investigation_graph_load_task_test_hooks.mutex
);
}
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
}
/**
* @brief Suspend éventuellement le worker après l'ouverture SQLite.
*/
static void investigation_graph_load_task_test_wait_after_open_hook(void)
{
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
investigation_graph_load_task_test_hooks.reached_after_open =
TRUE;
g_cond_broadcast(
&investigation_graph_load_task_test_hooks.condition
);
while (investigation_graph_load_task_test_hooks.pause_after_open &&
!investigation_graph_load_task_test_hooks.release_after_open)
{
g_cond_wait(
&investigation_graph_load_task_test_hooks.condition,
&investigation_graph_load_task_test_hooks.mutex
);
}
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
}
/**
* @brief Signale qu'une finalisation BackgroundTask a été exécutée.
*/
static void investigation_graph_load_task_test_mark_completion(void)
{
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
investigation_graph_load_task_test_hooks.completion_count++;
g_cond_broadcast(
&investigation_graph_load_task_test_hooks.condition
);
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
}
void investigation_graph_load_task_test_reset(void)
{
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
investigation_graph_load_task_test_hooks.pause_before_open =
FALSE;
investigation_graph_load_task_test_hooks.reached_before_open =
FALSE;
investigation_graph_load_task_test_hooks.release_before_open =
FALSE;
investigation_graph_load_task_test_hooks.pause_after_open =
FALSE;
investigation_graph_load_task_test_hooks.reached_after_open =
FALSE;
investigation_graph_load_task_test_hooks.release_after_open =
FALSE;
investigation_graph_load_task_test_hooks.completion_count =
0;
g_cond_broadcast(
&investigation_graph_load_task_test_hooks.condition
);
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
}
void investigation_graph_load_task_test_pause_before_open(void)
{
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
investigation_graph_load_task_test_hooks.pause_before_open =
TRUE;
investigation_graph_load_task_test_hooks.reached_before_open =
FALSE;
investigation_graph_load_task_test_hooks.release_before_open =
FALSE;
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
}
void investigation_graph_load_task_test_wait_until_before_open(void)
{
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
while (!investigation_graph_load_task_test_hooks.reached_before_open)
{
g_cond_wait(
&investigation_graph_load_task_test_hooks.condition,
&investigation_graph_load_task_test_hooks.mutex
);
}
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
}
void investigation_graph_load_task_test_release_before_open(void)
{
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
investigation_graph_load_task_test_hooks.release_before_open =
TRUE;
g_cond_broadcast(
&investigation_graph_load_task_test_hooks.condition
);
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
}
void investigation_graph_load_task_test_pause_after_open(void)
{
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
investigation_graph_load_task_test_hooks.pause_after_open =
TRUE;
investigation_graph_load_task_test_hooks.reached_after_open =
FALSE;
investigation_graph_load_task_test_hooks.release_after_open =
FALSE;
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
}
void investigation_graph_load_task_test_wait_until_after_open(void)
{
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
while (!investigation_graph_load_task_test_hooks.reached_after_open)
{
g_cond_wait(
&investigation_graph_load_task_test_hooks.condition,
&investigation_graph_load_task_test_hooks.mutex
);
}
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
}
void investigation_graph_load_task_test_release_after_open(void)
{
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
investigation_graph_load_task_test_hooks.release_after_open =
TRUE;
g_cond_broadcast(
&investigation_graph_load_task_test_hooks.condition
);
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
}
guint investigation_graph_load_task_test_get_completion_count(void)
{
guint completion_count =
0;
investigation_graph_load_task_test_ensure_initialized();
g_mutex_lock(
&investigation_graph_load_task_test_hooks.mutex
);
completion_count =
investigation_graph_load_task_test_hooks.completion_count;
g_mutex_unlock(
&investigation_graph_load_task_test_hooks.mutex
);
return completion_count;
}
#endif
/**
* @brief Enregistre une erreur littérale.
*/
static void investigation_graph_load_task_set_error_literal(
GError **error,
InvestigationGraphLoadTaskError error_code,
const char *message
)
{
if (error == NULL ||
*error != NULL)
{
return;
}
g_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR,
error_code,
message
);
}
/**
* @brief Enregistre une erreur en conservant le message d'origine.
*/
static void investigation_graph_load_task_set_nested_error(
GError **error,
InvestigationGraphLoadTaskError error_code,
const char *context,
const GError *nested_error
)
{
if (error == NULL ||
*error != NULL)
{
return;
}
g_set_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR,
error_code,
"%s : %s",
context,
nested_error != NULL
? nested_error->message
: "erreur inconnue"
);
}
/**
* @brief Libère les données privées d'un worker.
*/
static void investigation_graph_load_worker_data_free(
gpointer user_data
)
{
InvestigationGraphLoadWorkerData *worker_data =
user_data;
if (worker_data == NULL)
{
return;
}
g_free(
worker_data->database_path
);
g_free(
worker_data
);
}
/**
* @brief Libère un résultat qui n'a pas été transféré au callback.
*/
static void investigation_graph_load_result_free(
gpointer user_data
)
{
InvestigationGraphLoadResult *load_result =
user_data;
if (load_result == NULL)
{
return;
}
investigation_graph_model_free(
load_result->graph_model
);
investigation_graph_layout_free(
load_result->graph_layout
);
load_result->graph_model =
NULL;
load_result->graph_layout =
NULL;
g_free(
load_result
);
}
/**
* @brief Ajoute une référence interne à la tâche publique.
*/
static InvestigationGraphLoadTask *
investigation_graph_load_task_ref_internal(
InvestigationGraphLoadTask *load_task
)
{
if (load_task == NULL)
{
return NULL;
}
g_atomic_ref_count_inc(
&load_task->reference_count
);
return load_task;
}
/**
* @brief Détruit définitivement une tâche dont la dernière référence disparaît.
*/
static void investigation_graph_load_task_destroy(
InvestigationGraphLoadTask *load_task
)
{
BackgroundTask *background_task =
NULL;
gpointer user_data =
NULL;
GDestroyNotify user_data_destroy =
NULL;
if (load_task == NULL)
{
return;
}
background_task =
load_task->background_task;
user_data =
load_task->user_data;
user_data_destroy =
load_task->user_data_destroy;
load_task->background_task =
NULL;
load_task->callback =
NULL;
load_task->user_data =
NULL;
load_task->user_data_destroy =
NULL;
if (background_task != NULL)
{
background_task_cancel(
background_task
);
background_task_unref(
background_task
);
}
if (user_data != NULL &&
user_data_destroy != NULL)
{
user_data_destroy(
user_data
);
}
investigation_graph_layout_free(
load_task->completed_layout
);
load_task->completed_layout =
NULL;
g_free(
load_task->database_path
);
load_task->database_path =
NULL;
g_mutex_clear(
&load_task->mutex
);
g_free(
load_task
);
}
/**
* @brief Libère une référence interne ou publique.
*/
static void investigation_graph_load_task_unref_internal(
InvestigationGraphLoadTask *load_task
)
{
if (load_task == NULL)
{
return;
}
if (!g_atomic_ref_count_dec(
&load_task->reference_count
))
{
return;
}
investigation_graph_load_task_destroy(
load_task
);
}
/**
* @brief Libère le contexte de finalisation.
*/
static void investigation_graph_load_completion_context_free(
gpointer user_data
)
{
InvestigationGraphLoadCompletionContext *completion_context =
user_data;
if (completion_context == NULL)
{
return;
}
investigation_graph_load_task_unref_internal(
completion_context->load_task
);
completion_context->load_task =
NULL;
g_free(
completion_context
);
}
/**
* @brief Vérifie l'annulation du worker.
*/
static gboolean investigation_graph_load_task_check_cancelled(
GCancellable *cancellable,
GError **error
)
{
if (cancellable == NULL)
{
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INTERNAL,
"L'objet d'annulation du chargement est absent."
);
return TRUE;
}
return g_cancellable_set_error_if_cancelled(
cancellable,
error
);
}
/**
* @brief Exécute le chargement dans un thread secondaire.
*/
static gboolean investigation_graph_load_task_worker(
BackgroundTask *background_task,
GCancellable *cancellable,
gpointer worker_data_pointer,
gpointer *result,
GError **error
)
{
InvestigationGraphLoadWorkerData *worker_data =
worker_data_pointer;
Database *database =
NULL;
InvestigationGraphLoader *graph_loader =
NULL;
GraphNodePositionDao *position_dao =
NULL;
GPtrArray *positions =
NULL;
InvestigationGraphModel *graph_model =
NULL;
InvestigationGraphLayout *graph_layout =
NULL;
InvestigationGraphLoadResult *load_result =
NULL;
GError *component_error =
NULL;
guint position_index =
0;
if (background_task == NULL ||
cancellable == NULL ||
worker_data == NULL ||
worker_data->database_path == NULL ||
result == NULL ||
error == NULL)
{
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INTERNAL,
"Le contexte du worker de chargement est invalide."
);
return FALSE;
}
*result =
NULL;
if (investigation_graph_load_task_check_cancelled(
cancellable,
error
))
{
return FALSE;
}
#ifdef INVESTIGATION_GRAPH_LOAD_TASK_ENABLE_TEST_HOOKS
investigation_graph_load_task_test_wait_before_open_hook();
#endif
if (investigation_graph_load_task_check_cancelled(
cancellable,
error
))
{
return FALSE;
}
background_task_report_progress(
background_task,
0.05,
"Ouverture de la base de données"
);
database =
database_open(
worker_data->database_path
);
if (database == NULL)
{
g_set_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_DATABASE_OPEN,
"Impossible d'ouvrir la base SQLite : %s",
worker_data->database_path
);
return FALSE;
}
#ifdef INVESTIGATION_GRAPH_LOAD_TASK_ENABLE_TEST_HOOKS
investigation_graph_load_task_test_wait_after_open_hook();
#endif
if (investigation_graph_load_task_check_cancelled(
cancellable,
error
))
{
goto failure;
}
background_task_report_progress(
background_task,
0.20,
"Préparation du chargeur de graphe"
);
graph_loader =
investigation_graph_loader_new(
database,
&component_error
);
if (graph_loader == NULL)
{
investigation_graph_load_task_set_nested_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD,
"Impossible de préparer le chargement du graphe",
component_error
);
goto failure;
}
g_clear_error(
&component_error
);
background_task_report_progress(
background_task,
0.35,
"Chargement des entités et des relations"
);
graph_model =
investigation_graph_loader_load(
graph_loader,
&component_error
);
if (graph_model == NULL)
{
investigation_graph_load_task_set_nested_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD,
"Impossible de charger le graphe d'enquête",
component_error
);
goto failure;
}
g_clear_error(
&component_error
);
if (investigation_graph_load_task_check_cancelled(
cancellable,
error
))
{
goto failure;
}
background_task_report_progress(
background_task,
0.65,
"Chargement des positions du graphe"
);
position_dao =
graph_node_position_dao_new(
database,
&component_error
);
if (position_dao == NULL)
{
investigation_graph_load_task_set_nested_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD,
"Impossible de préparer le chargement des positions",
component_error
);
goto failure;
}
g_clear_error(
&component_error
);
positions =
graph_node_position_dao_list_all(
position_dao,
&component_error
);
if (positions == NULL)
{
investigation_graph_load_task_set_nested_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD,
"Impossible de charger les positions du graphe",
component_error
);
goto failure;
}
g_clear_error(
&component_error
);
graph_layout =
investigation_graph_layout_new();
if (graph_layout == NULL)
{
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_MEMORY,
"Impossible d'allouer la disposition du graphe."
);
goto failure;
}
for (position_index = 0;
position_index < positions->len;
position_index++)
{
const GraphNodePosition *position =
g_ptr_array_index(
positions,
position_index
);
if (position == NULL)
{
continue;
}
if (!investigation_graph_layout_set_position(
graph_layout,
graph_node_position_get_entity_identifier(
position
),
graph_node_position_get_x(
position
),
graph_node_position_get_y(
position
),
&component_error
))
{
investigation_graph_load_task_set_nested_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD,
"Impossible de construire la disposition du graphe",
component_error
);
goto failure;
}
g_clear_error(
&component_error
);
}
g_ptr_array_unref(
positions
);
positions =
NULL;
graph_node_position_dao_free(
position_dao
);
position_dao =
NULL;
investigation_graph_loader_free(
graph_loader
);
graph_loader =
NULL;
database_close(
database
);
database =
NULL;
if (investigation_graph_load_task_check_cancelled(
cancellable,
error
))
{
goto failure;
}
load_result =
g_try_new0(
InvestigationGraphLoadResult,
1
);
if (load_result == NULL)
{
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_MEMORY,
"Impossible d'allouer le résultat du chargement."
);
goto failure;
}
load_result->graph_model =
graph_model;
load_result->graph_layout =
graph_layout;
graph_model =
NULL;
graph_layout =
NULL;
if (investigation_graph_load_task_check_cancelled(
cancellable,
error
))
{
investigation_graph_load_result_free(
load_result
);
return FALSE;
}
background_task_report_progress(
background_task,
1.0,
"Graphe d'enquête chargé"
);
*result =
load_result;
return TRUE;
failure:
g_clear_error(
&component_error
);
investigation_graph_model_free(
graph_model
);
investigation_graph_layout_free(
graph_layout
);
if (positions != NULL)
{
g_ptr_array_unref(
positions
);
}
graph_node_position_dao_free(
position_dao
);
investigation_graph_loader_free(
graph_loader
);
if (database != NULL)
{
database_close(
database
);
}
return FALSE;
}
/**
* @brief Produit l'erreur publique correspondant à l'état final.
*/
static GError *investigation_graph_load_task_create_callback_error(
BackgroundTask *background_task
)
{
BackgroundTaskState background_state;
GError *background_error =
NULL;
background_state =
background_task_get_state(
background_task
);
if (background_state ==
BACKGROUND_TASK_STATE_CANCELLED)
{
return g_error_new_literal(
INVESTIGATION_GRAPH_LOAD_TASK_ERROR,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_CANCELLED,
"Le chargement du graphe d'enquête a été annulé."
);
}
background_error =
background_task_dup_error(
background_task
);
if (background_error == NULL)
{
return g_error_new_literal(
INVESTIGATION_GRAPH_LOAD_TASK_ERROR,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INTERNAL,
"La tâche a échoué sans fournir d'erreur."
);
}
if (background_error->domain ==
INVESTIGATION_GRAPH_LOAD_TASK_ERROR)
{
return background_error;
}
{
GError *callback_error =
g_error_new(
INVESTIGATION_GRAPH_LOAD_TASK_ERROR,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INTERNAL,
"Échec interne de la tâche de fond : %s",
background_error->message
);
g_error_free(
background_error
);
return callback_error;
}
}
/**
* @brief Finalise une exécution sur le contexte ayant lancé BackgroundTask.
*/
static void investigation_graph_load_task_on_completed(
BackgroundTask *background_task,
gpointer user_data
)
{
InvestigationGraphLoadCompletionContext *completion_context =
user_data;
InvestigationGraphLoadTask *load_task =
NULL;
InvestigationGraphLoadTaskCallback callback =
NULL;
gpointer callback_data =
NULL;
GDestroyNotify callback_data_destroy =
NULL;
InvestigationGraphModel *graph_model =
NULL;
InvestigationGraphLayout *graph_layout =
NULL;
GError *callback_error =
NULL;
gboolean disposed =
FALSE;
if (background_task == NULL ||
completion_context == NULL ||
completion_context->load_task == NULL)
{
return;
}
load_task =
completion_context->load_task;
g_mutex_lock(
&load_task->mutex
);
disposed =
load_task->disposed;
if (load_task->background_task ==
background_task)
{
load_task->background_task =
NULL;
load_task->running =
FALSE;
}
if (!disposed)
{
callback =
load_task->callback;
callback_data =
load_task->user_data;
callback_data_destroy =
load_task->user_data_destroy;
}
load_task->callback =
NULL;
load_task->user_data =
NULL;
load_task->user_data_destroy =
NULL;
g_mutex_unlock(
&load_task->mutex
);
if (callback != NULL)
{
if (background_task_get_state(
background_task
) == BACKGROUND_TASK_STATE_COMPLETED)
{
InvestigationGraphLoadResult *load_result =
background_task_get_result(
background_task
);
if (load_result != NULL &&
load_result->graph_model != NULL &&
load_result->graph_layout != NULL)
{
graph_model =
load_result->graph_model;
graph_layout =
load_result->graph_layout;
load_result->graph_model =
NULL;
load_result->graph_layout =
NULL;
}
else
{
callback_error =
g_error_new_literal(
INVESTIGATION_GRAPH_LOAD_TASK_ERROR,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INTERNAL,
"La tâche terminée ne contient aucun graphe."
);
}
}
else
{
callback_error =
investigation_graph_load_task_create_callback_error(
background_task
);
}
if (graph_layout != NULL)
{
g_mutex_lock(
&load_task->mutex
);
investigation_graph_layout_free(
load_task->completed_layout
);
load_task->completed_layout =
graph_layout;
graph_layout =
NULL;
g_mutex_unlock(
&load_task->mutex
);
}
callback(
load_task,
graph_model,
callback_error,
callback_data
);
}
if (callback_data != NULL &&
callback_data_destroy != NULL)
{
callback_data_destroy(
callback_data
);
}
g_clear_error(
&callback_error
);
/*
* Libère la référence à BackgroundTask possédée par le wrapper.
* La référence interne de BackgroundTask protège encore l'appel courant.
*/
background_task_unref(
background_task
);
#ifdef INVESTIGATION_GRAPH_LOAD_TASK_ENABLE_TEST_HOOKS
investigation_graph_load_task_test_mark_completion();
#endif
}
GQuark investigation_graph_load_task_error_quark(void)
{
return g_quark_from_static_string(
"investigation-graph-load-task-error-quark"
);
}
InvestigationGraphLoadTask *investigation_graph_load_task_new(
const char *database_path,
GError **error
)
{
InvestigationGraphLoadTask *load_task =
NULL;
g_return_val_if_fail(
error == NULL || *error == NULL,
NULL
);
if (database_path == NULL ||
database_path[0] == '\0')
{
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INVALID_ARGUMENT,
"Le chemin de la base SQLite est obligatoire."
);
return NULL;
}
load_task =
g_try_new0(
InvestigationGraphLoadTask,
1
);
if (load_task == NULL)
{
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_MEMORY,
"Impossible d'allouer la tâche de chargement."
);
return NULL;
}
load_task->database_path =
g_strdup(
database_path
);
if (load_task->database_path == NULL)
{
g_free(
load_task
);
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_MEMORY,
"Impossible de copier le chemin de la base SQLite."
);
return NULL;
}
g_atomic_ref_count_init(
&load_task->reference_count
);
g_mutex_init(
&load_task->mutex
);
load_task->running =
FALSE;
load_task->disposed =
FALSE;
return load_task;
}
void investigation_graph_load_task_free(
InvestigationGraphLoadTask *load_task
)
{
BackgroundTask *background_task =
NULL;
gpointer user_data =
NULL;
GDestroyNotify user_data_destroy =
NULL;
if (load_task == NULL)
{
return;
}
g_mutex_lock(
&load_task->mutex
);
load_task->disposed =
TRUE;
user_data =
load_task->user_data;
user_data_destroy =
load_task->user_data_destroy;
load_task->callback =
NULL;
load_task->user_data =
NULL;
load_task->user_data_destroy =
NULL;
if (load_task->running &&
load_task->background_task != NULL)
{
background_task =
background_task_ref(
load_task->background_task
);
}
g_mutex_unlock(
&load_task->mutex
);
if (user_data != NULL &&
user_data_destroy != NULL)
{
user_data_destroy(
user_data
);
}
if (background_task != NULL)
{
background_task_cancel(
background_task
);
background_task_unref(
background_task
);
}
investigation_graph_load_task_unref_internal(
load_task
);
}
gboolean investigation_graph_load_task_start(
InvestigationGraphLoadTask *load_task,
InvestigationGraphLoadTaskCallback callback,
gpointer user_data,
GDestroyNotify user_data_destroy,
GError **error
)
{
InvestigationGraphLoadWorkerData *worker_data =
NULL;
InvestigationGraphLoadCompletionContext *completion_context =
NULL;
BackgroundTask *background_task =
NULL;
GError *background_error =
NULL;
gboolean started =
FALSE;
g_return_val_if_fail(
error == NULL || *error == NULL,
FALSE
);
if (load_task == NULL ||
callback == NULL)
{
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INVALID_ARGUMENT,
"La tâche ou son callback final est invalide."
);
return FALSE;
}
worker_data =
g_try_new0(
InvestigationGraphLoadWorkerData,
1
);
completion_context =
g_try_new0(
InvestigationGraphLoadCompletionContext,
1
);
background_task =
background_task_new(
"Charger le graphe d'enquête"
);
if (worker_data == NULL ||
completion_context == NULL ||
background_task == NULL)
{
investigation_graph_load_worker_data_free(
worker_data
);
g_free(
completion_context
);
background_task_unref(
background_task
);
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_MEMORY,
"Impossible de préparer l'exécution asynchrone."
);
return FALSE;
}
worker_data->database_path =
g_strdup(
load_task->database_path
);
if (worker_data->database_path == NULL)
{
investigation_graph_load_worker_data_free(
worker_data
);
g_free(
completion_context
);
background_task_unref(
background_task
);
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_MEMORY,
"Impossible de copier le chemin SQLite pour le worker."
);
return FALSE;
}
completion_context->load_task =
investigation_graph_load_task_ref_internal(
load_task
);
g_mutex_lock(
&load_task->mutex
);
if (load_task->disposed)
{
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INVALID_ARGUMENT,
"La tâche de chargement a déjà été libérée."
);
goto start_failure_locked;
}
if (load_task->running)
{
investigation_graph_load_task_set_error_literal(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_ALREADY_RUNNING,
"Un chargement du graphe est déjà actif."
);
goto start_failure_locked;
}
investigation_graph_layout_free(
load_task->completed_layout
);
load_task->completed_layout =
NULL;
load_task->background_task =
background_task;
load_task->callback =
callback;
load_task->user_data =
user_data;
load_task->user_data_destroy =
user_data_destroy;
load_task->running =
TRUE;
started =
background_task_start(
background_task,
investigation_graph_load_task_worker,
worker_data,
investigation_graph_load_worker_data_free,
investigation_graph_load_result_free,
investigation_graph_load_task_on_completed,
completion_context,
investigation_graph_load_completion_context_free,
&background_error
);
if (!started)
{
load_task->background_task =
NULL;
load_task->callback =
NULL;
load_task->user_data =
NULL;
load_task->user_data_destroy =
NULL;
load_task->running =
FALSE;
investigation_graph_load_task_set_nested_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INTERNAL,
"Impossible de démarrer BackgroundTask",
background_error
);
goto start_failure_locked;
}
g_mutex_unlock(
&load_task->mutex
);
g_clear_error(
&background_error
);
return TRUE;
start_failure_locked:
g_mutex_unlock(
&load_task->mutex
);
g_clear_error(
&background_error
);
investigation_graph_load_worker_data_free(
worker_data
);
investigation_graph_load_completion_context_free(
completion_context
);
background_task_unref(
background_task
);
return FALSE;
}
void investigation_graph_load_task_cancel(
InvestigationGraphLoadTask *load_task
)
{
BackgroundTask *background_task =
NULL;
if (load_task == NULL)
{
return;
}
g_mutex_lock(
&load_task->mutex
);
if (load_task->running &&
load_task->background_task != NULL)
{
background_task =
background_task_ref(
load_task->background_task
);
}
g_mutex_unlock(
&load_task->mutex
);
if (background_task != NULL)
{
background_task_cancel(
background_task
);
background_task_unref(
background_task
);
}
}
gboolean investigation_graph_load_task_is_running(
const InvestigationGraphLoadTask *load_task
)
{
InvestigationGraphLoadTask *mutable_load_task =
NULL;
gboolean running =
FALSE;
if (load_task == NULL)
{
return FALSE;
}
mutable_load_task =
(InvestigationGraphLoadTask *) load_task;
g_mutex_lock(
&mutable_load_task->mutex
);
running =
mutable_load_task->running;
g_mutex_unlock(
&mutable_load_task->mutex
);
return running;
}
InvestigationGraphLayout *investigation_graph_load_task_take_layout(
InvestigationGraphLoadTask *load_task
)
{
InvestigationGraphLayout *graph_layout =
NULL;
if (load_task == NULL)
{
return NULL;
}
g_mutex_lock(
&load_task->mutex
);
graph_layout =
load_task->completed_layout;
load_task->completed_layout =
NULL;
g_mutex_unlock(
&load_task->mutex
);
return graph_layout;
}