/****************************************************************************** * @file eml_pipeline_task.c * @brief Pipeline d'analyse asynchrone complète pour fichier EML. ******************************************************************************/ #include "core/eml_pipeline_task.h" #include "core/file_hash.h" #include #include #include typedef struct { char *eml_path; char *processed_evidence_dir; char *evidence_id; char *exiftool; char *tesseract; char *pdfinfo; char *pdftotext; char *pdftoppm; guint document_analysis_limit; } EmlPipelineTaskData; static void eml_pipeline_task_data_free(gpointer user_data) { EmlPipelineTaskData *data = user_data; if (data == NULL) return; g_free(data->eml_path); g_free(data->processed_evidence_dir); g_free(data->evidence_id); g_free(data->exiftool); g_free(data->tesseract); g_free(data->pdfinfo); g_free(data->pdftotext); g_free(data->pdftoppm); g_free(data); } void eml_pipeline_result_free(EmlPipelineResult *res) { if (res == NULL) return; if (res->analysis != NULL) eml_analysis_free(res->analysis); if (res->mime_result != NULL) eml_mime_result_free(res->mime_result); if (res->bank_proposals != NULL) g_ptr_array_unref(res->bank_proposals); if (res->document_analyses != NULL) g_ptr_array_unref(res->document_analyses); if (res->warnings != NULL) g_ptr_array_unref(res->warnings); g_free(res); } static gboolean eml_pipeline_task_worker(BackgroundTask *task, GCancellable *cancellable, gpointer worker_data, gpointer *out_result, GError **error) { EmlPipelineTaskData *data = worker_data; g_return_val_if_fail(data != NULL, FALSE); background_task_report_progress(task, 0.10, "Analyse des en-têtes EML..."); if (g_cancellable_is_cancelled(cancellable)) return FALSE; EmlAnalysis *analysis = eml_analyzer_analyze_file(data->eml_path, error); if (analysis == NULL) return FALSE; background_task_report_progress(task, 0.30, "Calcul d'empreinte SHA-256..."); char *eml_hash = NULL; guint64 size = 0; file_hash_compute_sha256(data->eml_path, cancellable, &eml_hash, &size, NULL); char *target_dir = g_build_filename(data->processed_evidence_dir, "eml_attachments", eml_hash != NULL ? eml_hash : "default", NULL); g_free(eml_hash); background_task_report_progress(task, 0.50, "Extraction sécurisée des pièces jointes..."); if (g_cancellable_is_cancelled(cancellable)) { g_free(target_dir); eml_analysis_free(analysis); return FALSE; } EmlMimeResult *mime_res = eml_mime_extract_attachments_cancellable( data->eml_path, target_dir, cancellable, error ); g_free(target_dir); if (mime_res == NULL) { if (g_cancellable_is_cancelled(cancellable)) { eml_analysis_free(analysis); return FALSE; } /* Si l'extraction MIME échoue, on conserve quand même l'analyse des en-têtes (résultat partiel) */ g_clear_error(error); mime_res = g_new0(EmlMimeResult, 1); mime_res->attachments = g_ptr_array_new_with_free_func((GDestroyNotify) eml_attachment_free); mime_res->warnings = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add(mime_res->warnings, g_strdup("L'extraction MIME a échoué ou ne contient aucune pièce jointe.")); } background_task_report_progress(task, 0.75, "Analyse OCR et détection bancaire..."); GPtrArray *bank_proposals = g_ptr_array_new_with_free_func((GDestroyNotify) bank_proposal_free); GPtrArray *document_analyses = g_ptr_array_new_with_free_func( (GDestroyNotify) document_file_analysis_free); GPtrArray *pipeline_warnings = g_ptr_array_new_with_free_func(g_free); guint skipped_document_analyses = 0; for (guint i = 0; mime_res->attachments != NULL && i < mime_res->attachments->len; i++) { if (g_cancellable_is_cancelled(cancellable)) { eml_analysis_free(analysis); eml_mime_result_free(mime_res); g_ptr_array_unref(bank_proposals); g_ptr_array_unref(document_analyses); g_ptr_array_unref(pipeline_warnings); g_set_error_literal( error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "L'analyse EML a été annulée." ); return FALSE; } EmlAttachment *att = g_ptr_array_index(mime_res->attachments, i); if (att->extracted_path == NULL) continue; /* Analyse bancaire sur les fichiers texte/images compatibles */ if (g_str_has_suffix(att->extracted_path, ".txt") || g_str_has_suffix(att->extracted_path, ".eml")) { char *content = NULL; if (g_file_get_contents(att->extracted_path, &content, NULL, NULL)) { BankProposal *bp = bank_proposal_analyze_text(content, data->evidence_id); if (bp != NULL) { bp->extraction_id = g_strdup(att->part_index); g_ptr_array_add(bank_proposals, bp); } g_free(content); } } else if (ocr_analysis_mime_is_compatible(att->detected_mime) || g_strcmp0(att->detected_mime, "application/pdf") == 0 || g_strcmp0(att->content_type, "application/pdf") == 0) { if (document_analyses->len >= data->document_analysis_limit) { skipped_document_analyses++; continue; } DocumentAnalysisTools tools = { .exiftool = data->exiftool, .tesseract = data->tesseract, .pdfinfo = data->pdfinfo, .pdftotext = data->pdftotext, .pdftoppm = data->pdftoppm }; GError *analysis_error = NULL; DocumentFileAnalysis *document = document_file_analysis_run(&tools, att->extracted_path, att->content_type, att->detected_mime, TRUE, "fra+eng", cancellable, &analysis_error); if (document == NULL && analysis_error != NULL && g_error_matches(analysis_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_propagate_error(error, analysis_error); eml_analysis_free(analysis); eml_mime_result_free(mime_res); g_ptr_array_unref(bank_proposals); g_ptr_array_unref(document_analyses); g_ptr_array_unref(pipeline_warnings); return FALSE; } g_clear_error(&analysis_error); if (document == NULL) continue; g_ptr_array_add(document_analyses, document); const char *ocr_text = document->ocr != NULL ? document->ocr->text : NULL; if (ocr_text != NULL) { BankProposal *bp = bank_proposal_analyze_text(ocr_text, data->evidence_id); if (bp != NULL) { bp->extraction_id = g_strdup(att->part_index); g_ptr_array_add(bank_proposals, bp); } } } } background_task_report_progress(task, 1.0, "Analyse EML terminée avec succès."); EmlPipelineResult *res = g_new0(EmlPipelineResult, 1); res->analysis = analysis; res->mime_result = mime_res; res->bank_proposals = bank_proposals; res->document_analyses = document_analyses; res->warnings = pipeline_warnings; res->skipped_document_analyses = skipped_document_analyses; res->state = skipped_document_analyses > 0 ? DOCUMENT_ANALYSIS_STATE_PARTIAL : DOCUMENT_ANALYSIS_STATE_SUCCESS; if (skipped_document_analyses > 0) g_ptr_array_add(res->warnings, g_strdup_printf( "La limite d'analyses documentaires est atteinte : " "%u fichier(s) n'ont pas été analysés.", skipped_document_analyses)); if (out_result != NULL) *out_result = res; return TRUE; } BackgroundTask *eml_pipeline_task_new(const char *eml_path, const char *processed_evidence_dir, const char *evidence_id) { DocumentAnalysisTools tools = { .exiftool = "exiftool", .tesseract = "tesseract", .pdfinfo = "pdfinfo", .pdftotext = "pdftotext", .pdftoppm = "pdftoppm" }; return eml_pipeline_task_new_with_tools(eml_path, processed_evidence_dir, evidence_id, &tools); } BackgroundTask *eml_pipeline_task_new_with_tools( const char *eml_path, const char *processed_evidence_dir, const char *evidence_id, const DocumentAnalysisTools *tools) { return eml_pipeline_task_new_with_tools_and_limit( eml_path, processed_evidence_dir, evidence_id, tools, DOCUMENT_ANALYSIS_MAX_PIPELINE_ITEMS); } BackgroundTask *eml_pipeline_task_new_with_tools_and_limit( const char *eml_path, const char *processed_evidence_dir, const char *evidence_id, const DocumentAnalysisTools *tools, guint document_analysis_limit) { if (eml_path == NULL || processed_evidence_dir == NULL || tools == NULL) return NULL; if (document_analysis_limit == 0) return NULL; EmlPipelineTaskData *data = g_new0(EmlPipelineTaskData, 1); data->eml_path = g_strdup(eml_path); data->processed_evidence_dir = g_strdup(processed_evidence_dir); data->evidence_id = g_strdup(evidence_id); data->exiftool = g_strdup(tools->exiftool); data->tesseract = g_strdup(tools->tesseract); data->pdfinfo = g_strdup(tools->pdfinfo); data->pdftotext = g_strdup(tools->pdftotext); data->pdftoppm = g_strdup(tools->pdftoppm); data->document_analysis_limit = document_analysis_limit; BackgroundTask *task = background_task_new( "Analyse du message EML et de ses pièces jointes"); GError *start_error = NULL; if (task == NULL || !background_task_start(task, eml_pipeline_task_worker, data, eml_pipeline_task_data_free, (GDestroyNotify) eml_pipeline_result_free, NULL, NULL, NULL, &start_error)) { if (task != NULL) background_task_unref(task); else eml_pipeline_task_data_free(data); g_clear_error(&start_error); return NULL; } return task; }