diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index fe1e3b7..71fc697 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -268,3 +268,1302 @@ Le logiciel doit rester : - portable ; - testable ; - maintenable. + +# Feuille de route repensée — Labfy Investigation + +## Point de départ + +Le ticket **#031.1 — Fermeture propre de l’application** termine le premier socle GTK. + +À ce stade, Labfy Investigation sait : + +- créer une enquête ; +- ouvrir une enquête existante ; +- remplacer une session active ; +- afficher son arborescence ; +- fermer proprement l’application ; +- ouvrir et initialiser la base SQLite ; +- charger l’identité persistante de l’enquête ; +- gérer les transactions et les erreurs de base. + +La suite ne doit plus être pensée comme un simple gestionnaire de fichiers. + +## Vision du logiciel + +Labfy Investigation doit devenir un **poste de travail OSINT orienté enquête** capable de : + +- conserver les preuves originales ; +- extraire et rechercher des métadonnées ; +- créer des entités ; +- relier les données entre elles ; +- afficher les relations sous forme de graphe ; +- construire une chronologie ; +- effectuer des recherches locales ; +- lancer des outils OSINT externes ; +- interroger des services Internet ; +- conserver les résultats bruts ; +- normaliser les résultats ; +- documenter la provenance de chaque information ; +- produire un rapport exploitable. + +Architecture générale : + +```text +Interface GTK + ↓ +Contrôleurs de l’application + ↓ +Services métier + ├── preuves + ├── entités + ├── relations + ├── recherches + ├── tâches + └── rapports + ↓ +Adaptateurs + ├── SQLite + ├── système de fichiers + ├── outils CLI + ├── bibliothèques + └── API Internet +``` + +--- + +# Principes non négociables + +## SQLite reste la source de vérité + +Le graphe, les tableaux, la chronologie et les résultats de recherche sont des vues différentes des mêmes données. + +## Résultat brut et résultat normalisé sont séparés + +Chaque outil doit produire : + +```text +Sortie brute conservée et horodatée + + +Données normalisées utilisables dans Labfy +``` + +## Toute information doit avoir une provenance + +Une donnée exploitable doit pouvoir répondre à ces questions : + +```text +Qu’a-t-on trouvé ? +Quand ? +Avec quel outil ? +Avec quelle version ? +À partir de quelle requête ? +Quelle était la réponse brute ? +Quelle preuve ou source justifie l’interprétation ? +``` + +## Les outils externes sont optionnels + +Une dépendance absente ne doit pas empêcher Labfy de démarrer. + +Chaque capacité peut être : + +```text +Disponible +Absente +Trop ancienne +Non configurée +Désactivée +En erreur +``` + +## L’interface ne doit jamais être bloquée + +Les opérations longues doivent fonctionner en arrière-plan : + +- calcul d’empreinte ; +- copie de fichiers ; +- extraction de métadonnées ; +- requêtes réseau ; +- analyse de résultats ; +- génération de rapports. + +## Aucune commande shell construite avec une chaîne + +Les outils externes doivent être lancés avec `GSubprocess` et une liste d’arguments. + +## Les données déduites restent distinctes des données observées + +Labfy doit distinguer : + +```text +Observation directe +Résultat produit par un outil +Donnée importée +Déduction de l’enquêteur +Hypothèse +``` + +--- + +# Phase 1 — Consolider le noyau d’application + +## Ticket #032 — Factoriser le chargement d’une enquête + +Créer un service interne unique qui : + +1. ouvre une `InvestigationSession` ; +2. récupère le projet ; +3. construit l’arborescence ; +4. installe la session dans `Application` ; +5. conserve l’ancienne session en cas d’échec. + +Ce flux sera réutilisé par : + +- création d’une enquête ; +- ouverture manuelle ; +- enquêtes récentes ; +- arguments de ligne de commande ; +- restauration de session. + +## Ticket #033 — Afficher les erreurs dans GTK + +Créer un module commun d’affichage : + +- erreur ; +- avertissement ; +- confirmation ; +- information. + +Les erreurs techniques restent journalisées avec GLib, mais l’utilisateur doit recevoir un message graphique compréhensible. + +## Ticket #034 — Gestionnaire de tâches asynchrones + +Créer un modèle de tâche capable de gérer : + +- état ; +- progression ; +- annulation ; +- résultat ; +- erreur ; +- date de début ; +- date de fin. + +États recommandés : + +```text +En attente +En cours +Terminée +Échouée +Annulée +``` + +L’interface GTK doit rester fluide pendant l’exécution. + +## Ticket #035 — File de tâches et panneau d’activité + +Ajouter une file de tâches et une vue GTK permettant de suivre : + +- imports ; +- calculs d’empreintes ; +- recherches OSINT ; +- extractions de métadonnées ; +- exports. + +## Ticket #036 — Configuration de l’application + +Créer une configuration persistante pour : + +- chemins d’outils ; +- délais d’exécution ; +- activation des modules ; +- préférences d’interface ; +- paramètres réseau ; +- comportement des imports. + +Les secrets et clés API ne doivent pas être stockés en clair dans la base de l’enquête. + +--- + +# Phase 2 — Système d’outils externes + +## Ticket #037 — Registre des capacités et dépendances + +Créer un registre central capable de détecter : + +- présence d’un exécutable ; +- chemin réel ; +- version ; +- compatibilité minimale ; +- statut ; +- message d’erreur. + +Premiers outils candidats : + +```text +dig +host +whois +curl +openssl +file +exiftool +ffprobe +strings +``` + +## Ticket #038 — Interface commune des adaptateurs OSINT + +Définir les concepts opaques : + +```c +OsintTool +OsintRequest +OsintExecution +OsintResult +``` + +Un adaptateur devra pouvoir : + +- valider une requête ; +- vérifier sa disponibilité ; +- exécuter l’outil ; +- appliquer un délai maximal ; +- capturer `stdout` ; +- capturer `stderr` ; +- récupérer le code de retour ; +- analyser la sortie ; +- retourner un résultat normalisé. + +## Ticket #039 — Exécuteur sécurisé `GSubprocess` + +Créer un module générique qui lance un programme sans passer par un shell. + +Il doit gérer : + +- tableau d’arguments ; +- environnement contrôlé ; +- délai maximal ; +- annulation ; +- taille maximale des sorties ; +- code de retour ; +- erreurs de lancement. + +## Ticket #040 — Conservation des exécutions brutes + +Ajouter les tables et fichiers nécessaires pour conserver : + +- outil ; +- version ; +- cible ; +- paramètres ; +- date ; +- durée ; +- sortie standard ; +- sortie d’erreur ; +- code de retour ; +- SHA-256 de la sortie brute ; +- statut d’analyse. + +Exemple de stockage : + +```text +02_Preuves_Traitees/ +└── Resultats_Outils/ + └── __.json +``` + +## Ticket #041 — Vue des dépendances et capacités + +Créer une page GTK affichant : + +```text +ExifTool Disponible +dig Disponible +ffprobe Absent +Recherche sociale Non configurée +``` + +Une capacité absente doit expliquer comment l’activer sans bloquer l’application. + +--- + +# Phase 3 — Preuves et fichiers + +## Ticket #042 — Modèle opaque `EvidenceRecord` + +Champs initiaux : + +- identifiant ; +- nom original ; +- nom interne ; +- chemin relatif ; +- type ; +- taille ; +- SHA-256 ; +- date d’importation ; +- date de collecte déclarée ; +- source ; +- description ; +- statut d’intégrité. + +## Ticket #043 — Schéma SQLite et DAO des preuves + +Créer : + +- table `evidence` ; +- table `evidence_types` ; +- index ; +- contraintes ; +- DAO d’insertion ; +- DAO de lecture ; +- DAO de mise à jour autorisée. + +## Ticket #044 — Calcul SHA-256 par blocs + +Créer un module indépendant capable de traiter les gros fichiers sans les charger entièrement en mémoire. + +Ce ticket peut être synchronisé avec le cours de C. + +## Ticket #045 — Copie sûre et contrôlée d’un fichier + +Créer un service de copie qui : + +- valide le fichier source ; +- refuse les chemins dangereux ; +- évite les collisions ; +- copie vers une destination temporaire ; +- synchronise la copie ; +- recalcule son empreinte ; +- renomme atomiquement le fichier final. + +Ce ticket peut être synchronisé avec le cours Unix. + +## Ticket #046 — Import transactionnel d’une preuve + +Pipeline : + +```text +Validation +→ SHA-256 source +→ Copie temporaire +→ SHA-256 destination +→ Écriture SQLite +→ Validation de transaction +→ Renommage final +``` + +En cas d’échec, aucune preuve partiellement importée ne doit rester active. + +## Ticket #047 — Dialogue GTK d’import + +Ajouter : + +```text +Importer une preuve +``` + +Le formulaire doit permettre : + +- choix du fichier ; +- type ; +- source ; +- date de collecte ; +- description ; +- confirmation. + +## Ticket #048 — Liste et fiche détaillée des preuves + +Afficher sous forme de tableau : + +- nom ; +- type ; +- taille ; +- date ; +- source ; +- empreinte ; +- état d’intégrité. + +Une fiche détaillée doit afficher toutes les informations disponibles. + +## Ticket #049 — Vérification d’intégrité + +Recalculer les empreintes et détecter : + +- fichier absent ; +- taille modifiée ; +- empreinte modifiée ; +- chemin invalide ; +- doublon. + +--- + +# Phase 4 — Métadonnées et analyse locale + +## Ticket #050 — Premier adaptateur externe : ExifTool + +Ce sera le premier adaptateur complet validant toute l’architecture : + +```text +EvidenceRecord +→ tâche asynchrone +→ ExifTool +→ JSON brut +→ résultat stocké +→ métadonnées normalisées +``` + +## Ticket #051 — Modèle de métadonnées normalisées + +Créer un modèle générique : + +- clé ; +- valeur textuelle ; +- type de valeur ; +- namespace ; +- source ; +- outil ; +- confiance ; +- date d’extraction. + +## Ticket #052 — Extracteurs spécialisés + +Ajouter progressivement : + +- `file` pour le type réel ; +- `ffprobe` pour audio et vidéo ; +- extraction PDF ; +- extraction de documents bureautiques ; +- analyse EML ; +- analyse d’archives. + +## Ticket #053 — Recherche d’indicateurs dans les fichiers + +Détecter dans les métadonnées et contenus extraits : + +- emails ; +- domaines ; +- URL ; +- adresses IP ; +- téléphones ; +- pseudonymes ; +- noms d’utilisateurs ; +- coordonnées GPS. + +Les résultats doivent être proposés à l’enquêteur avant création d’entités. + +## Ticket #054 — Vue des métadonnées + +Afficher : + +- métadonnées brutes ; +- métadonnées normalisées ; +- filtres ; +- recherche ; +- provenance ; +- création d’entité depuis une valeur. + +--- + +# Phase 5 — Entités, observations et relations + +## Ticket #055 — Modèle opaque `EntityRecord` + +Types initiaux : + +```text +Personne +Pseudonyme +Adresse email +Téléphone +Domaine +Adresse IP +URL +Compte social +Organisation +IBAN +Cryptomonnaie +Document +Lieu +``` + +## Ticket #056 — Schéma et DAO des entités + +Créer : + +- `entity_types` ; +- `entities` ; +- `entity_attributes` ; +- contraintes ; +- index ; +- DAO. + +## Ticket #057 — Modèle `ObservationRecord` + +Une observation décrit une information découverte avec : + +- valeur ; +- date ; +- source ; +- méthode ; +- outil ; +- confiance ; +- statut de validation ; +- notes. + +## Ticket #058 — Interface de création et consultation des entités + +Permettre : + +- création manuelle ; +- modification ; +- recherche ; +- fusion contrôlée ; +- ajout d’attributs ; +- consultation des preuves associées. + +## Ticket #059 — Modèle opaque `RelationRecord` + +Une relation doit contenir : + +- source ; +- cible ; +- type ; +- direction ; +- date ; +- confiance ; +- provenance ; +- preuve associée ; +- note ; +- statut de validation. + +## Ticket #060 — Schéma et DAO des relations + +Créer : + +- `relation_types` ; +- `relations` ; +- contraintes ; +- index ; +- DAO. + +## Ticket #061 — Création et validation des relations + +Permettre : + +- création manuelle ; +- proposition automatique ; +- acceptation ; +- rejet ; +- modification ; +- justification. + +Aucune relation déduite automatiquement ne doit devenir définitive sans provenance. + +--- + +# Phase 6 — Recherche locale et requêtes + +## Ticket #062 — Index de recherche SQLite FTS5 + +Indexer : + +- preuves ; +- descriptions ; +- notes ; +- entités ; +- attributs ; +- observations ; +- métadonnées ; +- résultats OSINT ; +- événements. + +## Ticket #063 — Langage de filtres interne + +Créer un service de requêtes combinant : + +- texte ; +- type ; +- date ; +- source ; +- confiance ; +- outil ; +- statut ; +- relations. + +Exemples : + +```text +type:email source:exiftool +domain:example.org +confidence:<50 +evidence:"capture écran" +``` + +## Ticket #064 — Interface de recherche globale + +Ajouter : + +- barre de recherche ; +- filtres ; +- tri ; +- regroupement ; +- résultats paginés ; +- ouverture de la fiche correspondante. + +## Ticket #065 — Requêtes enregistrées + +Permettre d’enregistrer et de rejouer une recherche. + +## Ticket #066 — Recherche transversale et pivots + +Depuis une valeur, proposer : + +```text +Rechercher partout +Afficher les preuves liées +Afficher les entités liées +Afficher les relations +Lancer un enrichissement OSINT +``` + +--- + +# Phase 7 — Enrichissement OSINT réseau + +## Ticket #067 — Adaptateur DNS + +Fonctions initiales : + +- A ; +- AAAA ; +- MX ; +- NS ; +- TXT ; +- CNAME ; +- SOA ; +- résolution inverse. + +Stocker : + +- requête ; +- serveur utilisé ; +- date ; +- réponse brute ; +- réponses normalisées ; +- erreurs. + +## Ticket #068 — Adaptateur RDAP et WHOIS + +Extraire : + +- registraire ; +- dates ; +- serveurs de noms ; +- statuts ; +- contacts publics ; +- réseau IP ; +- ASN lorsque disponible. + +## Ticket #069 — Adaptateur TLS + +Analyser : + +- certificat ; +- sujet ; +- émetteur ; +- SAN ; +- dates ; +- chaîne ; +- empreintes ; +- protocoles observés. + +## Ticket #070 — Adaptateur HTTP + +Collecter : + +- statut ; +- redirections ; +- en-têtes ; +- titre ; +- type de contenu ; +- serveur déclaré ; +- empreinte de réponse ; +- liens principaux. + +## Ticket #071 — Recherche de sous-domaines + +Agrégation contrôlée de plusieurs sources : + +- DNS ; +- certificats ; +- données passives disponibles ; +- outils externes facultatifs. + +Les sources doivent rester identifiables séparément. + +## Ticket #072 — Archives du Web + +Ajouter un fournisseur permettant de rechercher : + +- captures anciennes ; +- dates disponibles ; +- URL historiques ; +- changements visibles. + +## Ticket #073 — Moteurs de recherche et recherche Web + +Créer une interface fournisseur pouvant utiliser : + +- API officielle ; +- service configuré ; +- ouverture assistée dans le navigateur ; +- import manuel de résultats. + +Labfy ne doit pas contourner les protections des moteurs. + +## Ticket #074 — Réseaux sociaux + +Créer un cadre générique pour : + +- comptes publics ; +- pseudonymes ; +- URL de profils ; +- publications publiques ; +- observations manuelles ; +- fournisseurs autorisés. + +Chaque plateforme pourra avoir : + +- un adaptateur API ; +- un adaptateur CLI ; +- un import manuel ; +- aucune automatisation si les règles l’interdisent. + +## Ticket #075 — Cache, quotas et limitation de débit + +Ajouter : + +- cache ; +- date d’expiration ; +- quotas ; +- pauses ; +- reprises ; +- erreurs temporaires ; +- délais entre requêtes. + +## Ticket #076 — Pivots OSINT + +Depuis une entité, proposer les recherches compatibles : + +```text +Domaine → DNS, RDAP, TLS, HTTP, archives +IP → reverse DNS, RDAP, ASN +Email → domaines, occurrences locales, fournisseurs configurés +Pseudonyme → moteurs, réseaux sociaux, dépôts publics +URL → HTTP, TLS, archives, métadonnées +``` + +## Ticket #077 — Révision des résultats avant intégration + +Une recherche OSINT doit produire une liste de propositions : + +- créer une entité ; +- compléter une entité ; +- créer une observation ; +- créer une relation ; +- ignorer. + +L’enquêteur garde le contrôle. + +--- + +# Phase 8 — Affichage analytique et graphe + +## Ticket #078 — Service de projection graphique + +Transformer les données SQLite en représentation graphique sans faire du graphe la source de vérité. + +Types de nœuds : + +- preuves ; +- entités ; +- événements ; +- résultats OSINT. + +Types d’arêtes : + +- relations validées ; +- relations proposées ; +- liens de provenance. + +## Ticket #079 — Première vue graphique + +Afficher un graphe simple avec : + +- nœuds ; +- liens ; +- libellés ; +- sélection ; +- zoom ; +- déplacement. + +Le premier moteur pourra s’appuyer sur Graphviz ou une bibliothèque compatible GTK. + +## Ticket #080 — Fiche contextuelle du graphe + +Cliquer sur un nœud ou une relation doit afficher : + +- identité ; +- attributs ; +- source ; +- confiance ; +- preuve justificative ; +- actions possibles. + +## Ticket #081 — Filtres du graphe + +Filtrer par : + +- type ; +- date ; +- source ; +- confiance ; +- statut ; +- profondeur ; +- enquêteur ; +- outil. + +## Ticket #082 — Organisation et regroupement + +Permettre : + +- regroupement manuel ; +- regroupement par type ; +- regroupement par domaine ; +- regroupement par période ; +- masquage de branches ; +- expansion d’un voisinage. + +## Ticket #083 — Vues graphiques enregistrées + +Enregistrer : + +- position des nœuds ; +- filtres ; +- regroupements ; +- annotations ; +- titre de la vue. + +## Ticket #084 — Tableaux et statistiques + +Créer des vues tabulaires et graphiques pour : + +- types de preuves ; +- entités ; +- domaines ; +- outils utilisés ; +- volume de résultats ; +- dates ; +- intégrité ; +- relations. + +--- + +# Phase 9 — Chronologie et notes + +## Ticket #085 — Modèle d’événement + +Champs : + +- date et heure ; +- précision ; +- fuseau ; +- description ; +- source ; +- entités ; +- preuves ; +- confiance. + +## Ticket #086 — Vue chronologique + +Afficher : + +- événements ; +- filtres ; +- regroupement ; +- preuves associées ; +- périodes sans date exacte. + +## Ticket #087 — Notes d’enquête + +Permettre : + +- notes globales ; +- notes liées à une preuve ; +- notes liées à une entité ; +- notes liées à une relation ; +- notes liées à un événement. + +## Ticket #088 — Hypothèses et pistes + +Créer un espace distinct pour : + +- hypothèses ; +- questions ouvertes ; +- pistes à vérifier ; +- statut ; +- priorité ; +- éléments favorables ; +- éléments contradictoires. + +--- + +# Phase 10 — Traçabilité et reproductibilité + +## Ticket #089 — Journal d’audit + +Tracer les opérations importantes : + +- création ; +- import ; +- modification ; +- suppression autorisée ; +- recherche OSINT ; +- création de relation ; +- validation ; +- export. + +## Ticket #090 — Rejouer une recherche + +Permettre de relancer une recherche avec : + +- même outil ; +- même version si disponible ; +- mêmes paramètres ; +- comparaison des résultats. + +## Ticket #091 — Manifeste de l’enquête + +Générer un manifeste contenant : + +- fichiers ; +- tailles ; +- empreintes ; +- versions d’outils ; +- base SQLite ; +- résultats bruts ; +- date de génération. + +## Ticket #092 — Contrôle de cohérence + +Détecter : + +- référence orpheline ; +- fichier absent ; +- résultat brut absent ; +- empreinte invalide ; +- relation sans provenance ; +- entité dupliquée. + +--- + +# Phase 11 — Rapports et exports + +## Ticket #093 — Rapport Markdown + +Générer : + +- identité de l’enquête ; +- résumé ; +- méthodologie ; +- preuves ; +- entités ; +- relations ; +- chronologie ; +- résultats OSINT ; +- empreintes ; +- limites. + +## Ticket #094 — Export PDF + +Transformer le rapport en document PDF transmissible. + +## Ticket #095 — Export du graphe + +Exporter une vue : + +- image ; +- SVG ; +- PDF ; +- annexe de rapport. + +## Ticket #096 — Archive autonome + +Créer une archive contenant : + +- rapport ; +- base ; +- manifeste ; +- résultats bruts ; +- preuves sélectionnées ; +- graphe ; +- journal d’audit. + +## Ticket #097 — Rédaction et anonymisation + +Permettre de produire une copie avec : + +- données masquées ; +- preuves exclues ; +- identifiants remplacés ; +- rapport adapté à la diffusion. + +--- + +# Phase 12 — Installation et distribution Ubuntu + +## Ticket #098 — Classification des dépendances + +Classer : + +```text +Obligatoires +Optionnelles +Recommandées +Fournies par une API +Indisponibles dans certains dépôts +``` + +## Ticket #099 — Script de compilation et installation + +Créer un dossier autonome avec : + +- détection de la distribution ; +- vérification des dépendances ; +- tentative d’installation ; +- compilation ; +- installation locale ; +- rapport clair des capacités indisponibles. + +## Ticket #100 — Paquet Debian + +Créer un paquet `.deb` contenant : + +- binaire ; +- icône ; +- fichier `.desktop` ; +- licence ; +- schémas ; +- dépendances obligatoires ; +- recommandations optionnelles. + +## Ticket #101 — Fonctionnement avec dépôts restreints + +Prévoir : + +- dépendances minimales ; +- modules facultatifs ; +- désactivation propre ; +- paquetage séparé si nécessaire ; +- documentation d’installation hors ligne. + +## Ticket #102 — Tests Ubuntu + +Tester : + +- Ubuntu LTS ; +- Wayland ; +- X11 ; +- machine sans outils de développement ; +- environnement hors ligne ; +- dépôts restreints ; +- utilisateur sans droits administrateur. + +--- + +# Jalons du projet + +## Jalon A — Socle professionnel + +Tickets : + +```text +#032 à #041 +``` + +Résultat : + +- contrôleur propre ; +- erreurs GTK ; +- tâches asynchrones ; +- registre d’outils ; +- exécution externe sécurisée ; +- résultats bruts conservés. + +## Jalon B — Gestion fiable des preuves + +Tickets : + +```text +#042 à #054 +``` + +Résultat : + +- import sûr ; +- SHA-256 ; +- intégrité ; +- métadonnées ; +- extraction d’indicateurs. + +## Jalon C — Enquête structurée + +Tickets : + +```text +#055 à #066 +``` + +Résultat : + +- entités ; +- observations ; +- relations ; +- recherche locale ; +- filtres ; +- pivots. + +## Jalon D — Poste de travail OSINT + +Tickets : + +```text +#067 à #077 +``` + +Résultat : + +- DNS ; +- RDAP ; +- TLS ; +- HTTP ; +- archives ; +- moteurs ; +- réseaux sociaux ; +- enrichissements contrôlés. + +## Jalon E — Analyse visuelle + +Tickets : + +```text +#078 à #088 +``` + +Résultat : + +- graphe ; +- tableaux ; +- chronologie ; +- notes ; +- hypothèses. + +## Jalon F — Transmission + +Tickets : + +```text +#089 à #102 +``` + +Résultat : + +- audit ; +- reproductibilité ; +- rapports ; +- exports ; +- installateurs Ubuntu. + +--- + +# MVP recommandé + +Le premier MVP réellement utile est atteint après le ticket **#066**. + +L’utilisateur pourra alors : + +1. créer ou ouvrir une enquête ; +2. importer des preuves ; +3. vérifier leur intégrité ; +4. extraire leurs métadonnées ; +5. créer des entités ; +6. relier les objets ; +7. rechercher dans toute l’enquête ; +8. effectuer des pivots locaux. + +Le second MVP, orienté OSINT réseau, est atteint après le ticket **#077**. + +--- + +# Ordre immédiat + +Ne pas commencer directement par le graphe ou les réseaux sociaux. + +Ordre recommandé : + +```text +#032 — Factoriser le chargement d’une enquête +#033 — Afficher les erreurs dans GTK +#034 — Gestionnaire de tâches asynchrones +#035 — File de tâches et panneau d’activité +#037 — Registre des dépendances +#038 — Interface commune des adaptateurs +#039 — Exécuteur GSubprocess +#040 — Conservation des résultats bruts +#042 — EvidenceRecord +``` + +Le ticket #036 sur la configuration peut être placé juste avant la première API nécessitant une clé. + +--- + +# Synchronisation avec les autres projets + +## C + +Tickets particulièrement adaptés au cours de C : + +- #034 gestion des tâches ; +- #039 exécution de processus ; +- #044 SHA-256 ; +- #045 copie robuste ; +- #053 extraction d’indicateurs ; +- #062 indexation ; +- #078 projection graphique. + +## Unix + +Tickets adaptés au cours système Unix : + +- #039 processus et signaux ; +- #045 fichiers et renommage atomique ; +- #049 intégrité ; +- #075 quotas et temporisation ; +- #089 journalisation ; +- #099 installation ; +- #100 paquet Debian. + +## OSINT + +Chaque nouveau fournisseur devra être précédé d’un apprentissage manuel : + +```text +Comprendre la technique +→ réaliser un exercice +→ documenter les limites +→ seulement ensuite intégrer l’outil +``` + +Labfy doit assister l’enquêteur, pas remplacer sa compréhension. + diff --git a/docs/tickets/closed/TICKET-31.1.md b/docs/tickets/closed/TICKET-031.1.md similarity index 100% rename from docs/tickets/closed/TICKET-31.1.md rename to docs/tickets/closed/TICKET-031.1.md diff --git a/docs/tickets/closed/TICKET-032.md b/docs/tickets/closed/TICKET-032.md new file mode 100644 index 0000000..b11f79f --- /dev/null +++ b/docs/tickets/closed/TICKET-032.md @@ -0,0 +1,537 @@ +# Ticket #032 — Factoriser le chargement et l’installation d’une enquête + +## Contexte + +Après le ticket #031.1, l’application sait : + +- créer une enquête ; +- ouvrir une enquête existante ; +- remplacer la session active ; +- fermer proprement l’application. + +Cependant, les flux « Nouvelle enquête » et « Ouvrir une enquête » dupliquent encore une partie importante de la logique : + +```text +investigation_session_open() +→ récupération du projet +→ récupération du chemin racine +→ investigation_tree_builder_build() +→ application_install_session() +→ nettoyage en cas d’échec +``` + +Cette duplication augmentera avec les futures fonctions : + +- enquêtes récentes ; +- ouverture depuis la ligne de commande ; +- restauration de session ; +- ouverture depuis un rapport ou une archive. + +## Objectif + +Créer dans `src/core/application.c` une fonction interne unique qui ouvre et installe une enquête à partir de son dossier racine. + +Contrat attendu : + +```c +static gboolean application_open_and_install_investigation( + Application *application, + const char *root_path, + GError **error +); +``` + +Cette fonction doit garantir : + +```text +succès : + Application devient propriétaire de la nouvelle session + Application devient propriétaire du nouvel arbre + ancienne session libérée + ancien arbre libéré + fenêtre mise à jour + +échec : + ancienne session conservée + ancien arbre conservé + nouvelle session libérée + nouvel arbre libéré + erreur transmise à l’appelant +``` + +--- + +# Travail à réaliser + +## 1. Ajouter un domaine d’erreur privé + +Dans `src/core/application.c`, ajouter un domaine d’erreur uniquement utilisé par le contrôleur. + +Exemple : + +```c +typedef enum +{ + APPLICATION_OPEN_ERROR_INVALID_ARGUMENT, + APPLICATION_OPEN_ERROR_INVALID_PROJECT, + APPLICATION_OPEN_ERROR_TREE_BUILD, + APPLICATION_OPEN_ERROR_INSTALL +} ApplicationOpenError; +``` + +Ajouter : + +```c +#define APPLICATION_OPEN_ERROR \ + application_open_error_quark() +``` + +Puis : + +```c +static GQuark application_open_error_quark(void) +{ + return g_quark_from_static_string( + "labfy-investigation-application-open-error" + ); +} +``` + +Le domaine reste privé à `application.c`. + +--- + +## 2. Créer la fonction factorisée + +Ajouter : + +```c +static gboolean application_open_and_install_investigation( + Application *application, + const char *root_path, + GError **error +); +``` + +La fonction doit : + +1. valider `application` ; +2. valider `application->main_window` ; +3. valider `root_path` ; +4. ouvrir une nouvelle `InvestigationSession` ; +5. récupérer son `InvestigationProject` ; +6. récupérer le chemin racine canonique ; +7. construire un nouvel `InvestigationTreeModel` ; +8. appeler `application_install_session()` ; +9. transférer la propriété uniquement en cas de succès. + +### Règle de propriété + +Avant `application_install_session()` : + +```text +la fonction possède new_session +la fonction possède new_tree_model +``` + +Après succès : + +```text +Application possède new_session +Application possède new_tree_model +``` + +Après échec : + +```text +la fonction doit libérer les objets qu’elle possède encore +``` + +### Validation de `GError` + +La fonction doit respecter la convention GLib : + +```c +g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE +); +``` + +L’utilisation de `g_return_val_if_fail()` est acceptable ici pour vérifier le contrat du développeur. + +Les erreurs utilisateur doivent être produites avec : + +```c +g_set_error() +g_set_error_literal() +g_propagate_prefixed_error() +``` + +--- + +## 3. Propager l’erreur de `InvestigationSession` + +Si : + +```c +investigation_session_open() +``` + +échoue, la fonction doit conserver l’erreur métier d’origine et lui ajouter du contexte. + +Exemple conceptuel : + +```text +Impossible d’ouvrir l’enquête : la base SQLite est absente +``` + +Ne pas remplacer l’erreur précise par un simple : + +```text +Erreur inconnue +``` + +lorsqu’un `GError` est disponible. + +--- + +## 4. Refactoriser l’ouverture d’une enquête existante + +`application_on_folder_selected()` ne doit plus contenir directement : + +```c +investigation_session_open() +investigation_tree_builder_build() +application_install_session() +``` + +Elle doit seulement : + +1. accepter l’annulation ; +2. appeler `application_open_and_install_investigation()` ; +3. journaliser l’erreur ; +4. libérer le `GError`. + +Structure attendue : + +```c +static void application_on_folder_selected( + const char *folder_path, + gpointer user_data +) +{ + Application *application = user_data; + GError *error = NULL; + + if (application == NULL || + 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" + ); + + g_clear_error(&error); + } +} +``` + +L’annulation ne doit pas produire de warning. + +--- + +## 5. Refactoriser la création d’une enquête + +`application_on_create_investigation()` doit conserver uniquement : + +1. la création physique du projet ; +2. l’appel à la fonction factorisée ; +3. le message spécifique indiquant que le projet existe sur le disque si son ouverture échoue. + +Le flux devient : + +```text +investigation_project_create() +→ application_open_and_install_investigation() +``` + +La fonction ne doit plus reconstruire elle-même : + +```text +session +projet +chemin canonique +arbre +installation +``` + +En cas d’échec après création, le dossier créé reste volontairement sur le disque. + +Le message doit être explicite : + +```text +L’enquête a été créée dans « ... », mais son ouverture a échoué : ... +``` + +--- + +## 6. Ne pas modifier `application_install_session()` + +Cette fonction conserve sa responsabilité actuelle : + +- valider les objets prêts à installer ; +- libérer l’ancien état ; +- transférer la propriété ; +- mettre à jour `MainWindow`. + +Le nouveau helper prépare les objets. + +`application_install_session()` réalise le remplacement final. + +--- + +# Pseudo-code de la fonction factorisée + +```c +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; + + valider les arguments; + + new_session = investigation_session_open( + root_path, + &session_error + ); + + si échec : + propager session_error avec contexte; + return FALSE; + + project = investigation_session_get_project( + new_session + ); + + valider project; + + canonical_root_path = + investigation_project_get_root_path(project); + + valider canonical_root_path; + + new_tree_model = + investigation_tree_builder_build( + canonical_root_path + ); + + si échec : + produire GError; + fermer new_session; + return FALSE; + + si application_install_session() échoue : + produire GError; + libérer new_tree_model; + fermer new_session; + return FALSE; + + return TRUE; +} +``` + +--- + +# Tests manuels + +## Création valide + +1. lancer l’application ; +2. créer une enquête ; +3. vérifier le titre ; +4. vérifier la barre d’état ; +5. vérifier l’arborescence. + +## Ouverture valide + +1. fermer et relancer ; +2. ouvrir l’enquête créée ; +3. vérifier le même résultat. + +## Remplacement valide + +1. ouvrir une enquête A ; +2. ouvrir une enquête B ; +3. vérifier que B remplace A. + +## Échec d’ouverture + +1. ouvrir une enquête valide A ; +2. tenter d’ouvrir un dossier invalide ; +3. vérifier que A reste active ; +4. vérifier que l’erreur est journalisée. + +## Échec après création + +Provoquer si possible un échec d’ouverture après création. + +Vérifier : + +- le dossier créé reste présent ; +- l’ancienne session reste active ; +- aucun double `free` ; +- aucun crash. + +## Annulation + +Annuler le sélecteur de dossier. + +Vérifier : + +- aucun warning ; +- aucun changement d’état ; +- aucun crash. + +--- + +# Critères d’acceptation + +- [ ] Une seule fonction ouvre une session et construit son arbre. +- [ ] La création utilise cette fonction. +- [ ] L’ouverture utilise cette fonction. +- [ ] `application_install_session()` n’est pas dupliquée. +- [ ] L’ancienne session reste active en cas d’échec. +- [ ] L’ancien arbre reste actif en cas d’échec. +- [ ] Les nouveaux objets sont libérés en cas d’échec. +- [ ] Les erreurs de `InvestigationSession` sont propagées. +- [ ] L’annulation ne produit pas d’erreur. +- [ ] Aucun code SQLite direct n’est ajouté. +- [ ] Aucun comportement GTK visible n’est cassé. +- [ ] `make` réussit. +- [ ] `make test` réussit. +- [ ] `git diff --check` ne retourne aucune erreur. + +--- + +# Audit attendu + +La logique d’ouverture ne doit apparaître qu’une seule fois : + +```bash +rg -n \ + 'investigation_session_open|investigation_tree_builder_build' \ + src/core/application.c +``` + +Résultat attendu : + +```text +une occurrence de investigation_session_open +une occurrence de investigation_tree_builder_build +``` + +La création et l’ouverture doivent appeler le helper : + +```bash +rg -n \ + 'application_open_and_install_investigation' \ + src/core/application.c +``` + +Vérifier l’absence de SQLite direct : + +```bash +rg -n \ + 'sqlite3_|#include ' \ + src/core/application.c +``` + +Résultat attendu : + +```text +aucune sortie +``` + +Vérifier le format : + +```bash +git diff --check +``` + +--- + +# Fichiers concernés + +```text +src/core/application.c +``` + +Aucune modification publique n’est normalement nécessaire dans : + +```text +include/core/application.h +``` + +--- + +# Commit attendu + +```bash +make clean +make +make test +git diff --check +git status --short +``` + +```bash +git add src/core/application.c +``` + +```bash +git diff --cached --stat +git diff --cached +``` + +```bash +git commit -m "refactor(core): centralize investigation loading" +git push +``` + +--- + +# Résultat attendu + +Après ce ticket, tous les futurs points d’entrée utiliseront le même flux : + +```text +Création +Ouverture manuelle +Enquêtes récentes +Ligne de commande +Restauration de session + ↓ +application_open_and_install_investigation() +``` + +Le ticket #033 pourra ensuite afficher graphiquement les `GError` déjà correctement produits par ce flux. diff --git a/docs/tickets/closed/TICKET-033.md b/docs/tickets/closed/TICKET-033.md new file mode 100644 index 0000000..bec51ae --- /dev/null +++ b/docs/tickets/closed/TICKET-033.md @@ -0,0 +1,694 @@ +# Ticket #033 — Afficher les erreurs dans l’interface GTK + +## Contexte + +Le ticket #032 a centralisé le chargement d’une enquête dans : + +```c +application_open_and_install_investigation() +``` + +Cette fonction produit maintenant des `GError` précis et conserve l’ancienne session en cas d’échec. + +Actuellement, les erreurs sont seulement écrites dans le terminal avec : + +```c +g_warning() +``` + +Un utilisateur qui lance Labfy Investigation depuis son menu d’applications ne verra pas ces messages. + +## Objectif + +Créer un module GTK réutilisable capable d’afficher un message d’erreur compréhensible dans une fenêtre modale. + +Le contrôleur doit conserver deux niveaux d’information : + +```text +Terminal : + message technique complet avec g_warning() + +Interface GTK : + titre clair + message compréhensible +``` + +Le module doit rester générique afin de pouvoir afficher plus tard : + +- erreurs ; +- avertissements ; +- informations. + +--- + +# Architecture attendue + +Créer : + +```text +include/views/application_message_dialog.h +src/views/application_message_dialog.c +``` + +Responsabilités : + +```text +Application + ├── décide quand afficher une erreur + ├── fournit le titre + └── fournit le message + +ApplicationMessageDialog + ├── construit la fenêtre GTK + ├── affiche le contenu + └── gère sa fermeture +``` + +Le module de dialogue ne doit connaître ni : + +- `Application` ; +- `InvestigationSession` ; +- SQLite ; +- les règles métier de l’enquête. + +--- + +# Travail à réaliser + +## 1. Créer l’en-tête public + +Créer : + +```text +include/views/application_message_dialog.h +``` + +Contenu attendu : + +```c +/****************************************************************************** + * @file application_message_dialog.h + * @brief Fenêtre modale affichant un message à l'utilisateur. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_APPLICATION_MESSAGE_DIALOG_H +#define LABFY_INVESTIGATION_APPLICATION_MESSAGE_DIALOG_H + +#include + +/** + * @brief Nature du message affiché. + */ +typedef enum +{ + APPLICATION_MESSAGE_DIALOG_ERROR, + APPLICATION_MESSAGE_DIALOG_WARNING, + APPLICATION_MESSAGE_DIALOG_INFORMATION +} ApplicationMessageDialogType; + +/** + * @brief Affiche une fenêtre modale contenant un message. + * + * Les chaînes sont copiées par les widgets GTK. + * + * @param parent_window Fenêtre parente, ou NULL. + * @param message_type Type de message. + * @param title Titre de la fenêtre. + * @param message Message principal. + */ +void application_message_dialog_present( + GtkWindow *parent_window, + ApplicationMessageDialogType message_type, + const char *title, + const char *message +); + +#endif +``` + +--- + +## 2. Créer l’implémentation GTK + +Créer : + +```text +src/views/application_message_dialog.c +``` + +Le dialogue doit être construit avec des widgets GTK4 simples afin de rester indépendant d’une API de dialogue plus récente. + +Structure visuelle recommandée : + +```text +┌─────────────────────────────────────────────┐ +│ Titre │ +│ │ +│ Message pouvant occuper plusieurs lignes │ +│ │ +│ [ Fermer ] │ +└─────────────────────────────────────────────┘ +``` + +Le module doit utiliser au minimum : + +- `GtkWindow` ; +- `GtkBox` ; +- `GtkLabel` ; +- `GtkButton`. + +Propriétés recommandées : + +```c +gtk_window_set_modal(dialog_window, TRUE); +gtk_window_set_destroy_with_parent(dialog_window, TRUE); +gtk_window_set_resizable(dialog_window, FALSE); +``` + +Si `parent_window` n’est pas `NULL` : + +```c +gtk_window_set_transient_for( + dialog_window, + parent_window +); +``` + +Le message doit : + +- revenir automatiquement à la ligne ; +- être aligné à gauche ; +- être sélectionnable pour permettre sa copie ; +- avoir une largeur raisonnable. + +Exemple : + +```c +gtk_label_set_wrap( + GTK_LABEL(message_label), + TRUE +); + +gtk_label_set_selectable( + GTK_LABEL(message_label), + TRUE +); + +gtk_label_set_xalign( + GTK_LABEL(message_label), + 0.0F +); + +gtk_label_set_max_width_chars( + GTK_LABEL(message_label), + 70 +); +``` + +--- + +## 3. Gérer les arguments invalides + +La fonction doit accepter : + +```text +parent_window == NULL +title == NULL +title vide +message == NULL +message vide +``` + +Valeurs de remplacement recommandées : + +```text +Titre : + Erreur + Avertissement + Information + +Message : + Aucun détail supplémentaire n'est disponible. +``` + +Le titre par défaut dépend de `message_type`. + +Une valeur inconnue de `message_type` doit être traitée comme une information ou un avertissement, sans crash. + +--- + +## 4. Ajouter un callback privé de fermeture + +Dans l’implémentation, ajouter : + +```c +static void application_message_dialog_on_close_clicked( + GtkButton *button, + gpointer user_data +); +``` + +Ce callback doit : + +1. ignorer proprement `button` ; +2. vérifier le pointeur de fenêtre ; +3. appeler `gtk_window_destroy()`. + +Exemple : + +```c +static void application_message_dialog_on_close_clicked( + GtkButton *button, + gpointer user_data +) +{ + GtkWindow *dialog_window = user_data; + + (void) button; + + if (dialog_window == NULL) + { + return; + } + + gtk_window_destroy( + dialog_window + ); +} +``` + +Aucune structure allouée manuellement ne doit être nécessaire pour ce premier dialogue. + +--- + +## 5. Différencier visuellement les types + +Le dialogue doit au minimum afficher un libellé distinct : + +```text +Erreur +Avertissement +Information +``` + +Une différenciation légère peut être ajoutée avec des classes CSS GTK existantes sur le titre ou le bouton. + +La couleur ne doit jamais être le seul moyen de distinguer le type. + +Aucune feuille CSS spécifique n’est nécessaire dans ce ticket. + +--- + +## 6. Ajouter la source au Makefile + +Ajouter : + +```text +src/views/application_message_dialog.c +``` + +à la liste des sources de l’application. + +Le module doit être compilé avec les mêmes options strictes : + +```text +-std=c17 +-Wall +-Wextra +-Wpedantic +-Werror +``` + +--- + +# Intégration dans `Application` + +## 7. Ajouter l’en-tête + +Dans : + +```text +src/core/application.c +``` + +ajouter : + +```c +#include "views/application_message_dialog.h" +``` + +--- + +## 8. Créer un helper privé dans `application.c` + +Ajouter : + +```c +static void application_present_error( + Application *application, + const char *title, + const char *message +); +``` + +Implémentation attendue : + +```c +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 + ); +} +``` + +Ce helper centralise le choix du parent GTK. + +--- + +## 9. Afficher les erreurs d’ouverture + +Dans : + +```c +application_on_folder_selected() +``` + +conserver le `g_warning()` existant. + +Ajouter ensuite : + +```c +application_present_error( + application, + "Ouverture impossible", + error != NULL + ? error->message + : "L'enquête sélectionnée n'a pas pu être ouverte." +); +``` + +L’erreur doit être affichée avant : + +```c +g_clear_error(&error); +``` + +L’annulation du sélecteur ne doit toujours afficher aucun message. + +--- + +## 10. Afficher les erreurs de création + +Dans : + +```c +application_on_create_investigation() +``` + +traiter les deux échecs. + +### Échec de création physique + +Conserver le `g_warning()` puis afficher : + +```text +Titre : + Création impossible + +Message : + L'enquête n'a pas pu être créée dans le dossier sélectionné. +``` + +Le message peut contenir : + +- le nom de l’enquête ; +- le dossier parent. + +### Projet créé mais impossible à ouvrir + +Conserver le `g_warning()` puis afficher un message indiquant clairement : + +```text +L'enquête a été créée sur le disque, mais Labfy n'a pas pu l'ouvrir. +``` + +Ajouter ensuite le détail du `GError`. + +Le message doit éviter de laisser croire que le dossier créé a été supprimé. + +Une chaîne dynamique peut être construite avec : + +```c +g_strdup_printf() +``` + +Elle doit être libérée après l’appel au dialogue. + +--- + +# Exemple d’intégration + +```c +char *user_message = NULL; + +user_message = g_strdup_printf( + "L'enquête a été créée dans :\n%s\n\n" + "Elle n'a cependant pas pu être ouverte :\n%s", + created_root_path, + error != NULL + ? error->message + : "erreur inconnue" +); + +application_present_error( + application, + "Enquête créée mais non ouverte", + user_message +); + +g_free( + user_message +); +``` + +--- + +# Gestion de plusieurs erreurs + +Ce ticket ne crée pas encore de file de notifications. + +Si plusieurs erreurs surviennent successivement, plusieurs fenêtres peuvent être affichées. + +La gestion centralisée des tâches et notifications arrivera avec les tickets #034 et #035. + +--- + +# Tests manuels + +## Dossier invalide + +1. lancer l’application ; +2. ouvrir une enquête valide A ; +3. cliquer sur `Ouvrir une enquête` ; +4. sélectionner un dossier qui n’est pas une enquête. + +Vérifier : + +- une fenêtre d’erreur apparaît ; +- le message explique l’échec ; +- A reste active ; +- le terminal contient encore le `g_warning()` ; +- aucun crash. + +## Annulation + +1. ouvrir le sélecteur ; +2. annuler. + +Vérifier : + +- aucun dialogue ; +- aucun warning ; +- aucun changement de session. + +## Création dans un emplacement invalide + +Provoquer si possible un échec de création : + +- dossier non accessible en écriture ; +- nom déjà utilisé ; +- emplacement invalide. + +Vérifier : + +- dialogue visible ; +- message compréhensible ; +- aucune session remplacée. + +## Projet créé mais ouverture échouée + +Provoquer si possible un échec après la création. + +Vérifier que le dialogue précise : + +- que le dossier existe ; +- où il se trouve ; +- que seule l’ouverture a échoué. + +## Fermeture du dialogue + +Tester : + +- bouton `Fermer` ; +- bouton de fermeture de la fenêtre ; +- fermeture de la fenêtre principale. + +Aucun segfault ne doit apparaître. + +## Répétition + +Produire plusieurs erreurs successives. + +Vérifier qu’une nouvelle erreur reste affichable après fermeture de la précédente. + +--- + +# Critères d’acceptation + +- [ ] Le module `application_message_dialog` existe. +- [ ] Le module ne dépend pas du cœur métier. +- [ ] Le dialogue est modal. +- [ ] Le dialogue possède une fenêtre parente lorsqu’elle existe. +- [ ] Le message revient à la ligne. +- [ ] Le message est sélectionnable. +- [ ] Les arguments `NULL` sont acceptés. +- [ ] Un bouton `Fermer` fonctionne. +- [ ] Les erreurs d’ouverture sont visibles dans GTK. +- [ ] Les erreurs de création sont visibles dans GTK. +- [ ] Les `g_warning()` techniques sont conservés. +- [ ] L’annulation ne produit aucun dialogue. +- [ ] L’ancienne session est conservée en cas d’échec. +- [ ] Aucun appel SQLite direct n’est ajouté. +- [ ] Aucun `exit()` direct n’est ajouté. +- [ ] `make` réussit. +- [ ] `make test` réussit. +- [ ] `git diff --check` ne retourne aucune erreur. + +--- + +# Audit attendu + +Vérifier l’utilisation du module : + +```bash +rg -n \ + 'application_message_dialog|application_present_error' \ + include/views/application_message_dialog.h \ + src/views/application_message_dialog.c \ + src/core/application.c \ + Makefile +``` + +Vérifier que les erreurs restent journalisées : + +```bash +rg -n \ + 'g_warning' \ + src/core/application.c +``` + +Vérifier l’absence de logique métier dans le dialogue : + +```bash +rg -n \ + 'Investigation|Database|sqlite3_' \ + include/views/application_message_dialog.h \ + src/views/application_message_dialog.c +``` + +Résultat attendu : + +```text +aucune sortie +``` + +Vérifier l’absence de sortie forcée : + +```bash +rg -n \ + '\bexit\s*\(' \ + src/core/application.c \ + src/views/application_message_dialog.c +``` + +Résultat attendu : + +```text +aucune sortie +``` + +--- + +# Fichiers concernés + +```text +include/views/application_message_dialog.h +src/views/application_message_dialog.c +src/core/application.c +Makefile +``` + +--- + +# Commit attendu + +```bash +make clean +make +make test +git diff --check +git status --short +``` + +```bash +git add \ + include/views/application_message_dialog.h \ + src/views/application_message_dialog.c \ + src/core/application.c \ + Makefile +``` + +```bash +git diff --cached --stat +git diff --cached +``` + +```bash +git commit -m "feat(ui): display application errors in GTK" +git push +``` + +--- + +# Résultat attendu + +Après ce ticket, l’utilisateur ne dépendra plus du terminal pour comprendre pourquoi une enquête n’a pas pu être créée ou ouverte. + +Le ticket #034 pourra ensuite introduire le gestionnaire de tâches asynchrones sans mélanger la présentation des erreurs avec l’exécution des traitements longs. diff --git a/include/views/application_message_dialog.h b/include/views/application_message_dialog.h new file mode 100644 index 0000000..a8be14a --- /dev/null +++ b/include/views/application_message_dialog.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * @file application_message_dialog.h + * @brief Fenêtre modale affichant un message à l'utilisateur. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_APPLICATION_MESSAGE_DIALOG_H +#define LABFY_INVESTIGATION_APPLICATION_MESSAGE_DIALOG_H + +#include + +/** + * @brief Nature du message affiché. + */ +typedef enum +{ + APPLICATION_MESSAGE_DIALOG_ERROR, + APPLICATION_MESSAGE_DIALOG_WARNING, + APPLICATION_MESSAGE_DIALOG_INFORMATION +} ApplicationMessageDialogType; + +/** + * @brief Affiche une fenêtre modale contenant un message. + * + * Les chaînes sont copiées par les widgets GTK. + * + * @param parent_window Fenêtre parente, ou NULL. + * @param message_type Type de message. + * @param title Titre de la fenêtre, ou NULL. + * @param message Message principal, ou NULL. + */ +void application_message_dialog_present( + GtkWindow *parent_window, + ApplicationMessageDialogType message_type, + const char *title, + const char *message +); + +#endif diff --git a/labfy-investigation b/labfy-investigation index 0825d5b..e6abeda 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/core/application.c b/src/core/application.c index d441454..e7bc5df 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -14,6 +14,7 @@ #include "views/create_investigation_dialog.h" #include "views/folder_dialog.h" #include "views/main_window.h" +#include "views/application_message_dialog.h" #include @@ -65,6 +66,37 @@ struct Application InvestigationTreeModel *tree_model; }; +/** + * @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 + ); +} + /** * @brief Installe une nouvelle session et son arbre dans l'application. * @@ -390,6 +422,14 @@ static void application_on_folder_selected( : "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 ); @@ -412,6 +452,8 @@ static void application_on_create_investigation( Application *application = user_data; char *created_root_path = NULL; + char *user_message = NULL; + GError *error = NULL; if (application == NULL) @@ -441,6 +483,25 @@ static void application_on_create_investigation( 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; } @@ -459,11 +520,31 @@ static void application_on_create_investigation( : "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 ); diff --git a/src/views/application_message_dialog.c b/src/views/application_message_dialog.c new file mode 100644 index 0000000..1a1f0b2 --- /dev/null +++ b/src/views/application_message_dialog.c @@ -0,0 +1,337 @@ +/****************************************************************************** + * @file application_message_dialog.c + * @brief Implémentation de la fenêtre modale de message. + ******************************************************************************/ + +#include "views/application_message_dialog.h" + +/** + * @brief Largeur minimale du dialogue. + */ +#define APPLICATION_MESSAGE_DIALOG_DEFAULT_WIDTH 480 + +/** + * @brief Message utilisé lorsqu'aucun détail n'est disponible. + */ +#define APPLICATION_MESSAGE_DIALOG_DEFAULT_MESSAGE \ + "Aucun détail supplémentaire n'est disponible." + +/** + * @brief Retourne le libellé correspondant au type de message. + * + * @param message_type Type du message. + * + * @return Libellé statique du type. + */ +static const char *application_message_dialog_get_type_label( + ApplicationMessageDialogType message_type +) +{ + switch (message_type) + { + case APPLICATION_MESSAGE_DIALOG_ERROR: + return "Erreur"; + + case APPLICATION_MESSAGE_DIALOG_WARNING: + return "Avertissement"; + + case APPLICATION_MESSAGE_DIALOG_INFORMATION: + return "Information"; + + default: + return "Information"; + } +} + +/** + * @brief Retourne le titre à utiliser pour le dialogue. + * + * @param message_type Type du message. + * @param title Titre fourni par l'appelant. + * + * @return Titre fourni ou titre par défaut. + */ +static const char *application_message_dialog_get_safe_title( + ApplicationMessageDialogType message_type, + const char *title +) +{ + if (title != NULL && + title[0] != '\0') + { + return title; + } + + return application_message_dialog_get_type_label( + message_type + ); +} + +/** + * @brief Retourne un message toujours valide. + * + * @param message Message fourni par l'appelant. + * + * @return Message fourni ou message par défaut. + */ +static const char *application_message_dialog_get_safe_message( + const char *message +) +{ + if (message != NULL && + message[0] != '\0') + { + return message; + } + + return APPLICATION_MESSAGE_DIALOG_DEFAULT_MESSAGE; +} + +/** + * @brief Ferme le dialogue lorsque l'utilisateur clique sur le bouton. + * + * @param button Bouton ayant reçu le clic. + * @param user_data Pointeur vers la fenêtre du dialogue. + */ +static void application_message_dialog_on_close_clicked( + GtkButton *button, + gpointer user_data +) +{ + GtkWindow *dialog_window = user_data; + + (void) button; + + if (dialog_window == NULL) + { + return; + } + + gtk_window_destroy( + dialog_window + ); +} + +void application_message_dialog_present( + GtkWindow *parent_window, + ApplicationMessageDialogType message_type, + const char *title, + const char *message +) +{ + GtkWindow *dialog_window = NULL; + + GtkWidget *main_box = NULL; + GtkWidget *type_label = NULL; + GtkWidget *title_label = NULL; + GtkWidget *message_label = NULL; + GtkWidget *button_box = NULL; + GtkWidget *close_button = NULL; + + const char *safe_title = NULL; + const char *safe_message = NULL; + const char *type_text = NULL; + + safe_title = + application_message_dialog_get_safe_title( + message_type, + title + ); + + safe_message = + application_message_dialog_get_safe_message( + message + ); + + type_text = + application_message_dialog_get_type_label( + message_type + ); + + dialog_window = GTK_WINDOW( + gtk_window_new() + ); + + gtk_window_set_title( + dialog_window, + safe_title + ); + + gtk_window_set_default_size( + dialog_window, + APPLICATION_MESSAGE_DIALOG_DEFAULT_WIDTH, + -1 + ); + + gtk_window_set_modal( + dialog_window, + TRUE + ); + + gtk_window_set_destroy_with_parent( + dialog_window, + TRUE + ); + + gtk_window_set_resizable( + dialog_window, + FALSE + ); + + if (parent_window != NULL) + { + gtk_window_set_transient_for( + dialog_window, + parent_window + ); + } + + main_box = gtk_box_new( + GTK_ORIENTATION_VERTICAL, + 12 + ); + + gtk_widget_set_margin_start( + main_box, + 20 + ); + + gtk_widget_set_margin_end( + main_box, + 20 + ); + + gtk_widget_set_margin_top( + main_box, + 20 + ); + + gtk_widget_set_margin_bottom( + main_box, + 20 + ); + + type_label = gtk_label_new( + type_text + ); + + gtk_widget_set_halign( + type_label, + GTK_ALIGN_START + ); + + gtk_label_set_xalign( + GTK_LABEL(type_label), + 0.0F + ); + + title_label = gtk_label_new( + safe_title + ); + + gtk_widget_set_halign( + title_label, + GTK_ALIGN_START + ); + + gtk_label_set_xalign( + GTK_LABEL(title_label), + 0.0F + ); + + gtk_label_set_wrap( + GTK_LABEL(title_label), + TRUE + ); + + message_label = gtk_label_new( + safe_message + ); + + gtk_widget_set_halign( + message_label, + GTK_ALIGN_FILL + ); + + gtk_label_set_xalign( + GTK_LABEL(message_label), + 0.0F + ); + + gtk_label_set_wrap( + GTK_LABEL(message_label), + TRUE + ); + + gtk_label_set_wrap_mode( + GTK_LABEL(message_label), + PANGO_WRAP_WORD_CHAR + ); + + gtk_label_set_selectable( + GTK_LABEL(message_label), + TRUE + ); + + gtk_label_set_max_width_chars( + GTK_LABEL(message_label), + 70 + ); + + button_box = gtk_box_new( + GTK_ORIENTATION_HORIZONTAL, + 0 + ); + + gtk_widget_set_halign( + button_box, + GTK_ALIGN_END + ); + + close_button = gtk_button_new_with_label( + "Fermer" + ); + + g_signal_connect( + close_button, + "clicked", + G_CALLBACK( + application_message_dialog_on_close_clicked + ), + dialog_window + ); + + gtk_box_append( + GTK_BOX(button_box), + close_button + ); + + gtk_box_append( + GTK_BOX(main_box), + type_label + ); + + gtk_box_append( + GTK_BOX(main_box), + title_label + ); + + gtk_box_append( + GTK_BOX(main_box), + message_label + ); + + gtk_box_append( + GTK_BOX(main_box), + button_box + ); + + gtk_window_set_child( + dialog_window, + main_box + ); + + gtk_window_present( + dialog_window + ); +} diff --git a/tests/test_database b/tests/test_database new file mode 100755 index 0000000..2a1ae25 Binary files /dev/null and b/tests/test_database differ diff --git a/tests/test_error b/tests/test_error new file mode 100755 index 0000000..1185635 Binary files /dev/null and b/tests/test_error differ diff --git a/tests/test_investigation_dao b/tests/test_investigation_dao new file mode 100755 index 0000000..0234ddd Binary files /dev/null and b/tests/test_investigation_dao differ diff --git a/tests/test_investigation_node b/tests/test_investigation_node new file mode 100755 index 0000000..b7614ef Binary files /dev/null and b/tests/test_investigation_node differ diff --git a/tests/test_investigation_project b/tests/test_investigation_project new file mode 100755 index 0000000..3bd6369 Binary files /dev/null and b/tests/test_investigation_project differ diff --git a/tests/test_investigation_record b/tests/test_investigation_record new file mode 100755 index 0000000..ba2880b Binary files /dev/null and b/tests/test_investigation_record differ diff --git a/tests/test_investigation_session b/tests/test_investigation_session new file mode 100755 index 0000000..04f8d66 Binary files /dev/null and b/tests/test_investigation_session differ diff --git a/tests/test_investigation_tree_builder b/tests/test_investigation_tree_builder new file mode 100755 index 0000000..d6cd484 Binary files /dev/null and b/tests/test_investigation_tree_builder differ diff --git a/tests/test_investigation_tree_model b/tests/test_investigation_tree_model new file mode 100755 index 0000000..2c823e1 Binary files /dev/null and b/tests/test_investigation_tree_model differ diff --git a/tests/test_statement b/tests/test_statement new file mode 100755 index 0000000..79b937b Binary files /dev/null and b/tests/test_statement differ diff --git a/tests/test_transaction b/tests/test_transaction new file mode 100755 index 0000000..3533f19 Binary files /dev/null and b/tests/test_transaction differ