From fbe1cd11fbce7e83239efe231ce6d5780f627a72 Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Wed, 15 Jul 2026 13:39:39 +0200 Subject: [PATCH] feat(database): initialize investigation database --- Makefile | 18 +- docs/tickets/closed/TICKET-022.md | 368 +++++++++++++++++ include/database/database.h | 46 +++ labfy-investigation | Bin 109712 -> 127528 bytes src/core/investigation_project.c | 25 +- src/database/database.c | 621 +++++++++++++++++++++++++++++ tests/test_database | Bin 0 -> 26856 bytes tests/test_database.c | 339 ++++++++++++++++ tests/test_investigation_project | Bin 35416 -> 45072 bytes tests/test_investigation_project.c | 38 ++ 10 files changed, 1430 insertions(+), 25 deletions(-) create mode 100644 docs/tickets/closed/TICKET-022.md create mode 100644 include/database/database.h create mode 100644 src/database/database.c create mode 100755 tests/test_database create mode 100644 tests/test_database.c diff --git a/Makefile b/Makefile index f426622..83cd829 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ TEST_NODE = tests/test_investigation_node TEST_TREE_MODEL = tests/test_investigation_tree_model TEST_TREE_BUILDER = tests/test_investigation_tree_builder TEST_PROJECT = tests/test_investigation_project +TEST_DATABASE = tests/test_database all: $(TARGET) @@ -60,19 +61,27 @@ $(TEST_TREE_BUILDER): \ $(TEST_PROJECT): \ tests/test_investigation_project.c \ - src/core/investigation_project.c - $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + src/core/investigation_project.c \ + src/database/database.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 + +$(TEST_DATABASE): \ + tests/test_database.c \ + src/database/database.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 test: \ $(TEST_NODE) \ $(TEST_TREE_MODEL) \ $(TEST_TREE_BUILDER) \ - $(TEST_PROJECT) + $(TEST_PROJECT) \ + $(TEST_DATABASE) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @./$(TEST_TREE_BUILDER) @./$(TEST_PROJECT) + @./$(TEST_DATABASE) @echo "Tous les tests sont valides." %.o: %.c @@ -86,6 +95,7 @@ clean: $(TEST_NODE) \ $(TEST_TREE_MODEL) \ $(TEST_TREE_BUILDER) \ - @./$(TEST_PROJECT) + $(TEST_PROJECT) \ + $(TEST_DATABASE) .PHONY: clean run test diff --git a/docs/tickets/closed/TICKET-022.md b/docs/tickets/closed/TICKET-022.md new file mode 100644 index 0000000..32b2cb5 --- /dev/null +++ b/docs/tickets/closed/TICKET-022.md @@ -0,0 +1,368 @@ +# Ticket #022 + +## Titre + +Initialiser la base SQLite d'une enquête. + +--- + +## Objectif + +Créer et initialiser correctement le fichier : + +```text +00_BaseDeDonnees/Enquete.sqlite +``` + +avec un premier schéma SQLite versionné. + +La base doit contenir les métadonnées minimales permettant d'identifier +l'enquête et la version du schéma. + +--- + +## Architecture + +```text +InvestigationProject + │ + ▼ +Database + │ + ▼ +SQLite +``` + +`InvestigationProject` orchestre la création de l'enquête. + +Le module `Database` est seul responsable de l'ouverture de SQLite, +de l'exécution du schéma et de la fermeture de la base. + +--- + +## Responsabilités + +### InvestigationProject + +Le module doit : + +- créer l'arborescence ; +- demander au module `Database` d'initialiser `Enquete.sqlite` ; +- considérer la création comme échouée si l'initialisation SQLite échoue ; +- nettoyer toute l'enquête créée en cas d'échec. + +### Database + +Le module doit : + +- ouvrir ou créer le fichier SQLite ; +- démarrer une transaction ; +- créer le schéma initial ; +- insérer les métadonnées ; +- valider la transaction ; +- annuler la transaction en cas d'erreur ; +- fermer proprement la connexion. + +--- + +## Nouveaux fichiers + +```text +include/database/database.h +src/database/database.c +``` + +Éventuellement : + +```text +include/database/schema.h +src/database/schema.c +``` + +si le schéma devient trop volumineux pour rester dans `database.c`. + +Pour ce ticket, un seul module `database.c` est acceptable. + +--- + +## Interface publique attendue + +```c +bool database_initialize( + const char *database_path +); +``` + +La fonction retourne : + +```c +true +``` + +si la base a été correctement initialisée. + +Elle retourne : + +```c +false +``` + +en cas d'erreur. + +--- + +## Schéma initial + +### Table `metadata` + +```sql +CREATE TABLE metadata +( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); +``` + +### Métadonnées obligatoires + +```text +schema_version +application +created_at +investigation_uuid +``` + +Valeurs attendues : + +```text +schema_version = 1 +application = Labfy Investigation +created_at = date UTC ISO 8601 +investigation_uuid = UUID unique +``` + +Exemple : + +```text +2026-07-14T18:42:15Z +``` + +--- + +## Table `investigation` + +Créer également une table minimale représentant l'enquête : + +```sql +CREATE TABLE investigation +( + id INTEGER PRIMARY KEY CHECK (id = 1), + name TEXT NOT NULL, + root_path TEXT NOT NULL, + created_at TEXT NOT NULL +); +``` + +La table contient une seule ligne correspondant à l'enquête courante. + +--- + +## Données nécessaires + +`database_initialize()` doit recevoir suffisamment d'informations pour +initialiser correctement la base. + +L'interface pourra donc évoluer vers : + +```c +bool database_initialize( + const char *database_path, + const char *investigation_name, + const char *investigation_root_path +); +``` + +Cette signature est préférée pour éviter que le module `Database` +reconstruise ou devine des informations métier. + +--- + +## UUID + +L'UUID doit être généré avec GLib : + +```c +g_uuid_string_random() +``` + +La chaîne retournée doit être libérée avec : + +```c +g_free(uuid); +``` + +--- + +## Date de création + +La date doit être produite en UTC avec GLib. + +Format attendu : + +```text +YYYY-MM-DDTHH:MM:SSZ +``` + +La date doit être enregistrée à la fois : + +- dans `metadata.created_at` ; +- dans `investigation.created_at`. + +--- + +## Transaction + +Toute l'initialisation doit se dérouler dans une transaction : + +```sql +BEGIN IMMEDIATE; +``` + +Puis : + +```sql +COMMIT; +``` + +En cas d'erreur : + +```sql +ROLLBACK; +``` + +Une base partiellement initialisée ne doit jamais être considérée comme valide. + +--- + +## Intégration avec InvestigationProject + +`investigation_project_create()` ne doit plus créer un fichier vide avec : + +```c +g_file_set_contents(...) +``` + +Il doit construire le chemin de la base puis appeler : + +```c +database_initialize( + database_path, + investigation_name, + investigation_path +); +``` + +Si l'appel échoue : + +- le fichier SQLite éventuel est supprimé ; +- tous les dossiers créés sont supprimés ; +- `investigation_project_create()` retourne `NULL`. + +--- + +## Validation + +À partir de ce ticket, `investigation_project_validate()` doit toujours +vérifier la présence du fichier SQLite, mais pas encore son contenu SQL. + +La validation du schéma sera ajoutée dans un prochain ticket dédié. + +--- + +## Hors périmètre + +Ce ticket ne doit pas : + +- créer les tables Preuves ; +- créer les tables Entites ; +- créer les relations ; +- gérer les migrations ; +- ouvrir une enquête existante ; +- modifier GTK ; +- exposer directement `sqlite3 *` hors du module Database. + +--- + +## Contraintes techniques + +- C17 ; +- SQLite3 ; +- GLib ; +- aucune dépendance GTK ; +- aucun état global ; +- requêtes SQL centralisées dans `database.c` ; +- fermeture garantie de la connexion ; +- transaction obligatoire ; +- documentation Doxygen ; +- compilation sans warning. + +--- + +## Tests + +Créer : + +```text +tests/test_database.c +``` + +Le test doit : + +- créer un dossier temporaire ; +- initialiser une base SQLite ; +- vérifier que le fichier existe ; +- ouvrir la base en lecture ; +- vérifier la présence de la table `metadata` ; +- vérifier la présence de la table `investigation` ; +- vérifier `schema_version = 1` ; +- vérifier `application = Labfy Investigation` ; +- vérifier que `created_at` n'est pas vide ; +- vérifier que l'UUID n'est pas vide ; +- vérifier la ligne unique de la table `investigation` ; +- vérifier le nom et le chemin racine ; +- vérifier qu'une initialisation sur un chemin invalide échoue ; +- nettoyer complètement les fichiers temporaires. + +Faire également évoluer : + +```text +tests/test_investigation_project.c +``` + +pour vérifier que la base créée n'est plus vide. + +--- + +## Critères d'acceptation + +- [ ] Le projet compile sans warning. +- [ ] `make test` reste entièrement valide. +- [ ] `Enquete.sqlite` est une vraie base SQLite. +- [ ] La table `metadata` existe. +- [ ] La table `investigation` existe. +- [ ] `schema_version` vaut `1`. +- [ ] Une date UTC est enregistrée. +- [ ] Un UUID est généré. +- [ ] L'enquête est enregistrée dans la base. +- [ ] L'initialisation est transactionnelle. +- [ ] Toute erreur provoque un nettoyage complet. +- [ ] Aucun type `sqlite3 *` n'est exposé publiquement. +- [ ] Aucune dépendance GTK. + +--- + +## Commit attendu + +```text +feat(database): initialize investigation database +``` diff --git a/include/database/database.h b/include/database/database.h new file mode 100644 index 0000000..c254658 --- /dev/null +++ b/include/database/database.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * @file database.h + * @brief Interface publique d'initialisation de la base SQLite d'une enquête. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_DATABASE_H +#define LABFY_INVESTIGATION_DATABASE_H + +#include + +/** + * @brief Initialise la base SQLite d'une enquête. + * + * La fonction crée ou ouvre le fichier SQLite indiqué, puis initialise + * transactionnellement le schéma minimal de l'enquête. + * + * Les informations suivantes sont enregistrées : + * + * - version du schéma ; + * - nom de l'application ; + * - date de création UTC ; + * - UUID de l'enquête ; + * - nom de l'enquête ; + * - chemin racine de l'enquête. + * + * Aucun handle sqlite3 n'est exposé au code appelant. + * + * @param database_path + * Chemin complet du fichier Enquete.sqlite. + * + * @param investigation_name + * Nom de l'enquête. + * + * @param investigation_root_path + * Chemin complet du dossier racine de l'enquête. + * + * @return true si la base a été correctement initialisée, + * sinon false. + */ +bool database_initialize( + const char *database_path, + const char *investigation_name, + const char *investigation_root_path +); + +#endif diff --git a/labfy-investigation b/labfy-investigation index d5a654ed3e6b17ea7638ad372b4bb32b6f9d795d..93e2c018e10e31da0ef9ee32c33dc2e34153e5b6 100755 GIT binary patch delta 46221 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 S%Pouh#T-Z1-(+3CnDJk5G5LuA delta 36272 zcmZ`?2Y3`!*Pc6DvuT^M$)*>Q5JE|4p+hLa5Lzf==mF^{(nS;(q)0$CIKWs?u_G3u zlz;^TDos&DLBv7?g!%9$}5FJvpp zSkMYx?NgdT?UsfzFG#~B$`a;|*eH~5=u+9j#zZ>O@E5fZrSYlb0vep{QK3Cp`O^MUZs+Cz_|5RFnU8lQ) z(`S`&VES?98ZR?eS%PYYa?4z*pstPwB2pEv9%$}Hg)k8pMw51}bP z?o@x;jVh@PFZ1If-jXl4tL;=J%i|8e=H}PA_D#-w$z3M%FznoM5O39{U^jb*%_Cemd9sGPIZ_7mcjAn>bCk!T&ZuW!W)a)oh|Ue#_GI#Mrlh7uEBUD%A??cI)ZRjd z3E9fSmnS}ZnPie)3-K>O()P6*umqW+%^rd0#ZSd5BufW@>7%bvb`44; z9ki31V(rp-N$}F^(0lMtSJW)g3OGrDkIvr|d};vien_*+3*hqvUl72T3ckd^Q~lCn zp(rybh~FgmnF0I_FzBYj0Ab9Bw-v0VjKQ9!{H?#osE_hGysR8^+!RH0=*9Bh?z(*|8{Fdmvs9(wz ziZWfH*-705KQn+ID)_|#{B*%r1n^4)zbSzCC_+&gpm$0Y27aZ1-(}#N82D-fKh3}&9qn zFH&US9l{B(zr>&j5egEe1|CbF|5Ik*!~CoyO)>CJ13%NiM;Q18m16!=t0E1G#fAb= z27aZ1k2dfX2Hs`hpD^%ZtI4&S{5;wpYbdbUuaG3Sfv+_1aRz>ufsZ%v)doJnz#la5 ziNx#uf7GBzG88yz;FAq}je$=w@LmI-YT#=Pe42sx+%zcC4GQUP4N%uKGVpc-pJCvg z20qikyA6Dnflt+Vk3Bk>lIR0 z1Ao%McQf!c2EM?+dkuVd17B<4dvKn&Uy^Pb6g>?Eq<1tx8R})=?FPQkz&j0mZv*c( z@V6QGRF6SXWKd)q_&x?c&%hTO`1S_AuYoTx@cj&Yk>EYlvi=4|iJ`y%17B+32O9V? z17Bj`rx^G_27ac2A6&owVHOw^LktBL8~C9Hex-rG-N08E_+bYA2?KwJpGW_1GAK&@ z3T!|O{BQ$bY2Zf~_+18mq=BzC@S_a;LE>rrQ5TFhD2^Hmj4|*h4g6RGUt{3Q47}IC zk2CPK2HrE?ptxyJ+-cyYZ5p6JCm49UfuCsLod$lAfp;7D$r`WCzf^-_ilIQZfuCyN z^9=lS1K-}j-(}zn4E)^&zG$0PKktGW21SXXz&!@O)WFX)@MQ*mmVuvQ;Ab27nL1DP z&oL+#=n8Oi4g6vQKhMCgH1P8ce1(C(*T6qv;1_@w?Z+%IDDE>9*lgez8u&^Bf4_m> zW#At$@YM!>Q9bXG4jL2>)+?l=2Hqbf^d}AcVuO8+fnQ?ay$1dv17FK|EM+vRml_l| z4F#4Ncxk%^$bN-^w;T9}x23gi${sHW@gU@)<<)cLx}!x)Kem6w?d#ee23?o=8IIv8 z1vn$j2fVMQ4xh|@gc*!|M>&3%uuQm`<97&~2v>5vg|M0MCXQbqYytN8Dmd{J5mqu- z%<+0DIFfKR$E^rQ5w7Gohj29E zO&q5acG0-@Rd6Dn2-^Jn7IPd)nD+d>nH)O^(_Y_K#<5J8_V~UMj&EU5PNBWMuYlug zglSLj%j5V0VcN_4QaL_DI0;UW0ZyDEBAKwn@iD?Fglqpt88}Qhm2eHm`v|8IKFaaC zgwqLEbNmkBMuaOl-a^t_#whg z2$yiYkT7kzeFYrPCQMsxUmnNP2-6nZm&);YV9%81L^wGyf(&R&?UOhjOgNWt?caP1 z5T-4(uZH6ugj*6m%5g`+tq50h+=_5(!j&B75N<nq{-7FMh&wBz*^aD0t0?R0&49A6+zJ6vBX$7dGN zAnHPdlM|=NpetdC<70%o5w877$96mxQ63>gvSv+%JI8|#}lsR_#MJ`60YQU3*iZbH~oqF>oQ*;g7#&;3U2Te z;YoxSbNo2r$%JQeyoxYgjeKPsKSX#c;S!D)5}ro5faBSOrxVWOcp72acKK5OME!M{ z2t$XCMgE$nor z&@RYV!0|Q0_=gN|e1R~^NWRikPMjeE0|^=6_!MC*F314K#|UGsKnDJx3>+rBm~aip z`v@-~e3au6_bR>mG#=Cx*P}XDPeguwQj74EWiY^YV@;QGm=~?FJVc_y&AG0k)s`D* zE^S1D>T1GM7L(maLc)GHw{JW6e&zi>3G(sz%C~)znz}$r^IG3tw6x3Pl&hlhU*?7u zt?qII4X<-82IajIvF^8ATBxKIkCV6DP^OMdQ(i94jct!0i^>ac7OgJy?xDJh$`5$w z{Ha_lZkuovt5MOKwkIKW3)L=?Ugd_;sqfu#^etse-+cDH@>Ac|Y_sC(_mDjAcjd`` zlh_B3_n#g90#-YC(~2ZrSC;foi(AFHeS2v8@FDrqv_t+--s|5auHwJ75q~KE>pzw~ zrHmfXJ85qsye?gIQkoZsyxqbI>#yU-YfANi?tS(Xx5jmttM(U_f1}m0>{?yj(nXh~ zc`+z=z*ue?G#6vR?lrPQuPMa?dq%}3pevt7q?92_PDre>X<)o(Ft`0zOFI1yUn5|P zj2767R^NWJXiez-*hLhTe@>;_!<}CX{36DM5$`8let=VJLL+hUEh^uKvfd*uNxH}? z4t;bxdben)(xJo^x96(pyfu~@TzdTdcTSmH(pFCWU3s>onfyq1<+BpETy#}ASCZen z5^W`qJV4ZRf`YogmdL$`*1Nj!dO33Tg*O(QvgDpJPfcid2}3Fb z<~8K+HLW^sbVS)Syoi0R_=cylI~B)>ZW%+`QwH}2&aXEvAfPo`{wkEaN5n>Z;gr{U zv7;Wyapj2--Pu{?)QF*Sm`}+YnIQl4i_(8&y!^>8ST7dl_xXkQL_ZX`Nul(CkLA_j z%Qm1Vyra;z)e=|*EFVlTTH|`AgOaw+UHF|BZ5TrZd1zGH`!n~}NB4G6^WMQ*hhVv< z{05#Da8L4sUZrBx9qhQ`9hIGamsVzmx2IlaA;PGy{W`wrU+0$ho!7^$JkYD$GdjyW z`5cDl+Tv*C*}JXEw$TOhw`Y~#N4IvH&r%OvznIKJMSt+|c=?P{Fs6k(;Jh+(OrpE2 zsC?1pqVjp0CKRnUXVNa57SNW}O8bW!-Nkc?iL?n0V8o{vZ6KFd&vR(K{H2M8Y=3U> zE()VGtqff0-G7F{FFc49?x3WXc#fASJm^oI1S?DlW!6}Cr-P!Bsb~=&B_IxXzw_yX zY9KFc$r-0LWi~Q~3TjY6jdIV}`0;xA=mzCi_{-mtDO^NUURVoROWs$aH-V3$oi*Sv zEqOcN)7nYnqh^nuyGMSe+h4`rtbu3tE?MMPn<9mKIhsq_sLZd_^-{Z87+p;)Vtn&=+uHL~%8|h(1!o zV>+o_%u4kb(gS12;4w_p6WFpr3@@J7V~9lzMQd)~h5EMQ#tQDRM0_byalrc&1~#SY zpq8p#w87{Aqg_R-izsO)5$=O+Xtsofz!ZzcF3LzP@4dEg#d~-SR6GG74tVP@Wc09U zKS}5Q)U`T8sT-|_-AykE;(+&Ul;o+qn2j-XQggyhe5L5ltoDvy z`8|{+O2Tqm=Gm-b92-m)XLj?_#)1+K!+xykQV#5zY4uYg`Y|fcf_- z{;TrVggo|!a(Y4{`$GA9LjO(-V~9t>r{x@~TkKL)kwd)j`WrAVK)ULk!4lw}j! zurtc;i7CzG?|IC%7y@JS_|Snn-WvyzWR$G+_v)*Pc~WNR^QSbLoPVO^Pr5B5={xS? zr1xv8xBMd%;vIeb2ZBG6;Zw@d(bMrm-&zu z5pNl93PwqJ4?Ev8dOuf^CMU*xiEN@ClrM1l101CcoV;JY{Rd^%l<2tdZ>e_bz?vf3 z)qLY!gQj}3xz>H=q^|V}t%QiYe2Wn`hCAa8P!^yy2ma8 zX{_}VrJ|lZL#@{>>475ozFQ*`HB+5kKqG*gmO7Du>Zz`{iQja~}Y@M&@KweOUiY58*eNhQ30bLFvVm%_h5sf*$8%4bP;W!3Zyc1hVfJs!U`J3PI+ zb1T)k#0_&sW-*sG*5KzuMxe0a|no$sme=I@V~p?!Je_`)ex<`E&Z8**g zqr%Zj?LxOw&@WusaCeDk9R9-?IFb zK5YgwG~!#$4ZZCo$zP_k{;nE}D818^K{LCth05BQ(OoT~ zGx-3p^S-7&q(Y((<{YK^`7)J_*6@+1Ra5?r(tml1BF{?k`22NYy}?q2!Uwz)XvD3t z%*)iEg*p?rQJA`J(6a@7nNp3`HV08){W||v%M$$eMNn)J1?lW{+%+R`F48UM&B*{BQ}zo??povWKgqle~Q$7 z;XT|ibqPwz>~8FSrDArp=LWK_t=$`WI6On`HD*;kQ=l;m>X{;qnN-h|Xw1-hrc?yn z-n*4Z{|w6$xr_U}i$5EmQ|wy%3cO4H)BNHQzx5_qdlQthIj-nW!1{;SP2zw1N*OmN z+VhN2Q9+}NyDup4kCDE7P|$cQE%ILGLrsjo0zJgi`Z`1FBP{V&|EG0Mz4d9&=X#B$ zrQXN?)BNwx{0(}$-a60wbpvaCJ^GwmQ}3=#H}>udvZkhC)L*%!S2|jGdhW9=@6uMM zYUnFS-&w=B$Y^*vaCzZNI(+Q3GHqU(XH6Qe?ic)h@B~I=d(mauVR;|;`>7gcy%GgQR*M^e;^MR3%#lT>Eke!Xo`uq z#$xr??p<&6BjH1-s)Qf!YGu>CuGr$o*x65~Ifk0~C5yXy@?c$E!1h0% z&oZOY+e&}x+!~tol5+2TO|zXa;5Q|FWuns>*wo;rWL`YF>?*z`q6D^74&T?f`(BRt zb^KAjT3EIc6L9<0x?yN*EF+RMCnF9htrq67o64OFr?9QcXA9rP%LxzOAIDlKPu!oz z#wqXK-<{1+{=UCW=&6s<2(drhqjY&-r04NZs9Cs+q>ZVz&VR_~U!m7Ke?QmNYV=B; ztiqe#t^VPAQy((qwtU}e%XK_!ofJN!nl}F4kF>h{qW&ilN-6R^8@A>Z}&M2=fYUcSU z4i7&{=t+oJ9cTxIP6|OMeMq`C2HlH%C#CNQ$z&OsL}(_pd+6I+>QyIIPfz%lF1jgk zSEIz-&E#uA2wYmz|)O%tx+4ob+uYnp@64XsRC`2i?j)KKF? z9^ttSG;JDczR)!B9;i|ps&;S{uQV7MUt6tGcSEbk>a9#<6|Px18(K}RxBAOX8K4BM zafSM=JiNbqYSB;&Zo*7Y4rM^oPz%mB)X*3aSv%5D^E(CwdN2sxQw7b%1t@%|p~*3B zGIv1H$ThC*4OP1|71j7$L(Q|AhWcq$L(Q`K8s|Yn7fH*ESf$zGsd&L^!{T%~eV_8~ z;x=rQa%FLP;*+q(=)g~279EwsmpLhO{D~fJIh5v0;uBx2<_7$MAq;9LY4q5L#)@+1 zl9rwgG#eNappE^LV%j|Rq0V6b@!9Y{*mzI!`e?n>4JX0S|73GF)y5y41YGRB{9?Z~ z1{eEzs8Fk`26Z_pl198TzJI@|GHb;G}j3l zjt+PeKU9uBlrXv*H3d&crMW5I7r6t9mZ~D7mV3x0?$Px?=jM7x`x~#X8Nu9&E{F5+ zwT)>1i4X9^>gu!v<<6z$@{jwJ%S+p{?MmZiqcd*2ueXSH<#tikCb0em;!b7rvNkMP z`C(ai_JESIJT7`Ruj3?oWn#;f>80l>fN}&$LvCIKitv$8fz_;D26YI@|_<##Q+DfnKKOq9<{V(_ZwzLmL|<-4c~O2sZi-?rwg zHelgJY4%XOl2`6weU-}c^wl7?S7*kJYp=KK>M|hX z->IwX1^Er+1jrkZ%OGR6)z!TOISuk-$bFDML;eJ5#-r7U9Vid^KgeE?r+3zQ>L%di zsVX>t`~&hO$kk}&$B+k+|DPdWMN`cdNjd;e_u0Dj>H)Rzim2@xW2YBFI|E=OLqmaPfg`1Nr`Ub#byyJ9T-A2ew zkncl&0{K1U1<1c4!)xm55*?D%2C_5caL7@Ro>y@408Tpn1P73hL%t9B4CMEa+adpk zj5q@a!N?F~XUHXI;Q;c;&nOQ$;5^Dhj`h{mRYOj{P*+z2xeZdn1Gz^p*43p#re3P6 zD}cwv* zza9MN(0|ZCzss-x8vJxj4cvPurkyV9b>L}Rl~udqdq-mpnFiI+21N(@i_$m7&q80? zKtIH<9}4}6t;#<;;yrV5dK{*Ckp5PfnliHGtpNWECg!CEuFm^iZHNBtx9jT8G|>Ox z*MAND98BEz8t8ZGdYg0|24UNjX}jVlMPm`@i%Gn$f$d|utwh_)525dZBK5Jh_s2RE z`tP^b)tzoo{*=G`Tvo(zwS$r|>1&)miB)4o0|!g|4z5FA2mPo9 z`aAskXskk=cGlI=FAMzv-p$oKnQfr&zmo#+4CSYFbuy&i)bu}@jQQvSpNn;gw%hgk zzJC2W=*MCmde&H#{G>mW?a==Kee(vn%GPt`k-mn(1uR@Q8aTM>cW@p0YFtu&X`rw1 z>!Yz?F2~Y85VAhNVzb_WHsH5o0o>o9{9aK$8;ckWMqsNmrh&n5e+6@)f8rx0rz+mF z4yVt+w7_V3Keyz`+7A5(&_COtu}|uamA;0)6V}{q#`5x8+(9b1>(GA={VlZF-v%K?=G7QY2LrT}3G0G!5?DEftlnr41IHc^{ zkuG;Wtek?l`6)_({8UNanJl+G0_$YC%p5xlo!B`I))N4 z%J)0%^2lS#IWXIfD~_sU`TOHYN3#6WaitjI+;L?Z#Kq&vDu~+S$`**j?}*F1A1`s=K{)i}(<=1~yWl8#fcti}R={DP*Z-UVZsWsw zoG{hzf5-X*Yz&O}(*S%X0ACNl7VHy@_i$koEv~w2H+p7tsE!}k=-@ssJDu~)G%B>^G^35$u{O2v?gHEOR^JMw!5M|ouxe;46 z8@j&GCoe?V@_BN4Wx58jo8+I`WKGg^qffO-xd2y{$x8ee$q^gzRE<7=*45oZGF?e) z{hVC;2L7~6j3+;jDC};9946!>A?FLZLdXq5z9QsKA@>V;LdbJM-VoA?pCiyGM#wBp zdZc_o^cHfMkduU*FXRd#HwgKPkUNFkFXRa!&k1=$NGreIBLE?@xWrJ%7esF%hY2}J z$oWF95ORZ%uL!wQ$o)c|5b~UmH-xmd78wvSizE%jd_nXUa+r{lgq$zr3L!TL`HGM` zh1@UX2_er3c|%AmU+j?qA+sR)RLB=ZZy|>XIZ4R*Laq>UgOINXxl_pfLY@%voRBw! zwBn~s^obENtF4%d`GV*z3b|j%6GEO7@`jMsd=Y?Lgv|1YlYAk23pq^4NkYyS za)po^gnUKFokH#x@`RA*guEf7wY>;H$n-36k}qU$AvH5xdbcZ&f8EkCyn{xh$+T+3 zH3O0*yGj2ODhoSl>QAAKjn}je+-z$LQekIJyTwd}g?flnS6?a6)Nh##YP`%PNePtnwqBPejC(Kjjb@D56^ zZ(7RVO55)Jrjo^t5Lr#t3?;U)2`&WI8mm(9Z9?x(LYv~(j@Go2G$Wo2<+W3!$fh`9 zFG^eG-wimwN!`SX^<9=KPphsv5I=aV5 z(!I&Qp;7YMBFA>+hjUHbh4`XtQ9>o>MoC%6P&sQIiincWr@&j+^LNNs3KaGHgzWLS zShJ|$WZIEhPvBC{qB*czi{XR061-42t(Dsvd4FT>MPkUN`a}4Hve;c|aUrRwuTYB1 z0#ZXikCiFD{mI-8*|xc`&8dthc%`T2Wt87pdvpPuDHpQbQ-yMcUpb$YmCBbFn#;vK zAHV2IAjc2YJMM}bfO(<0Xh_qZN}kZquh;iNvZiu5JAzPS7b_TqCf>QWi}}x znKEly`@ScZ0%57bd8wPq&*-j1UTP%2)m>?KX^#9%FXfF(Z_2hXW#q5>3%8y1r7in8shg7+V8yVf@Dx!b_t!=EcavW<6PjD};v zt-sbA^XB_&jidEtkw2T@HsRZq*x!m=?cl$O<3nV}w6@B_x5JiE%G}@DdyXQap5at4 zI>*utcD+*80h!kBqU+$j82%QC*_U8_a?#gcNv6wDCTT9hLoZ473oQUX(=d>b3>5z* zwiiSYZ8OYv8^vz6TV8>K7_(*SjJbrK2U40|Z!{Xc19gX($H>ZT>jxoMgyVd$t1q2* zg-i1g?ohr6Q_F^UGL5xc_EHIE9S;#i=az#w$SrWNws8^>bWHszip6@)65kt9Tf4+* z)tD=gKC|8IqRvai!FqT2yJYn>#;5g>1bY5rX@!HEh9`63AX$DSgP&nwYmzVkOvG6_ zZ%5DK>|N=!L+E{E7QAo)*QTH*^R%m6@ zRkATH#KjF)X7nZgs-{fqy`yjRai}a*VVH%khr#WKDkCnp_Qbyqvj-&k6nZ?kcQk$N zYw1B&yb2ciAXTvky(5`2ak*xad<0#>T&9UoBsf`o&|5gMzfQW8h$FC*?0h}UXhcQr zk~|-US(b?|BHT8Sezg1r{v~0^c%)=0=tF>T8$9GP;)6`m>*NQ~e~Ki_i7bXvKvx~c z*AB-5%Fq=V-S#8$74`yma#eRCIojYfcxpr#=CszXp@@{9MM}1P z=n{EeEF~v{49-%O$?L#IWQOK};azUK3-N`IoiCu6ytXW)R2Xy56 zCr;VMzp$(Z$Dw==#se^|eZmu*1Kd-`6&~v^;b?zv7ECT^1+?CT!EG9r*A`fR2VbPa zBI_9Bub4S$jL2(Ctcx)U`?64)Q>L|>Jt+eRYC-&@3F#z4pf*Zt8E>L#ZKWqlO(>b%XEml(UGB`HX*_0cKv;4Y7D|TrSP?E=DKCMx)?X>l zVVcsF(tppvjqz;-qP7)aK*(W<e?_;#jiTqzjmfSc33s6SxQy8F*ES&RQN=SC@>maa8nf6cP zNZO@@6z85M6A9mOsuZ20=OSYh)RSlW&|jzN7pC1@hD%);ovmzMA})DY3xPKUBwXp?RW;>R z(VD>PY9Z<>3c8@?$SmRZaJXn060LP-1#IhpBePA9 zMg+F|a5UM%aWvZ|V7G0y#kpvt%lE3yEi69l5HGP8K{6@)lzO{`Z3`U@yNDK&?o6})$6I~eU zX^NVH6OpGeGM@{4|I;4@rQltNdAK~C3L4l|V28>}0$3lqYM`tJu)Wc^LitTUI}_ts z3V9W*yICyx{3w`opHg+^9qM8`i*YxG|Hy8kS4mM$@qW*>OVziaojL}QMz#)3!8kQ) zpCNikXuhB3EifM=UmZgG`)S^G+yQ`Y5jvqE9cCpuCzLW0(noC`#NvyK;2A+hJWK|M+)jvMvdSH$-G^?9>iM3h9h-xJ$U)WWYz;_QrJss7YA!5 zzdk~p?qGTHf)VO74wm7j+gSVxYb$JM&PRk`wMu1OMyTI9SZg<}R*hH~jWjG}QiKb^ zJDFpIni0%0SPQjJFiVgx4p%1yvzGF%;p&sYELYxhyLvd7wdnsfT<7uGgM}}09}?G6 zAZ#s;q3v|h`WP3%==y8SYp8%$iTa0G0QiioDhy%OOj{)^e3h{9Rl>qo2@78(EPR!) z_*aRRVfrdz(N>AjQ|jv#Q;J|Ln zRi}ruPSNAZ?VZ*aV9cllu2`|^yWy;F=MK<><;7Z&2KF7wYr{U`SGW($iKS6z_fX;6 ztxd>;3bPC3!D@+8yE~c7{R8scOm^HsYKlOc8o@uSGjN9#`aTuz=%#Y^7u5Am)@mwE z^3Xy)qOIB-chHyLT46p#tlA`ZTnwPKS?-W&7V@ezYi*i4;sWU2YF-4UE=}yPX0c@7 z+dr?ZHn%z^g2l5P>U|NcZGT!DzkOp2+J(<>P2kG&5efv+Li}waozhj(OzDrL1@gcw zVJ{x`!JaNpL5tPDB3MF48p?U1SNgDxG~Ia(68bSjldFV(z6r?`O=4UjlVJ{H6=|{RN{Odeba~! z6q;*NU)%=M4B3QBt1CPmrL-p?pQ0CNiqt>Q)xsEFl@P^h*-(BVwel%qPvYV}W>v99`Qa|(J)n*%PE;t|uvinj7; zW4Z;UoQ(Lwv*M|197#BruM^QMndXb*H=LWCI5VZ;z^|Fk==lCP5AKXZI9)T#ScLr# z05hLTQO<4fDw%&pNfyn4-TWHOD^CnZPV;&~u^hO~jmgf@c!=!4NVW}$%)w2$aHj^u{4Q_Of8nYQC< ztQ)x&Glz0fM%HIhTngEST8m}Jy~L1;6;rN4w5zJ)(S~#oUFbv0LSH8Oce3W0b&55B zYIFJ!Q1~o2DMGaCl;dkM58>W<&>=sld)+Jsv-PB#wPn54usGJr^C%(-pW!Aa@wAm; zvzhh1$rL1hL#GK$6?*uUz_(emSt|D(2O4dM<2Fv2`*z1ROTNi zl`ySc>4~5VS6%W1>%di>j8qY5{W|cD9_Y0g zI^|6(LNiinkHFf7Q1t!Bs+IA~mCJL`jyH(rd-`Mq!Lma2X^%6%s=r*)UJu2j`fqP|8EK$8ErER+Iyw#Qni1O@r0G?)M7D%ShM6CaBME`;; zn+Bo{k)NdvM^>Bza9$MorQHW$X=a7utH^z_R0u?m`6G`#-tB%zS7l$1<{jD+XT39^R5 z8iW7r1*pI_80mD4XW>+^$VSa}O=OPC$Y+V|3#wu=iy%~LGo=DeWqd4@*%+Z|EQoYd zY|l_=ce4;0c{3%`A5_J@oQKg`Q&l6JXMCLV5#mhR3E*@sV0Mbyww{u*kcCrcNVfB8 zLL>HxC!;ZdoQy0pc`5o=mfykAByUBY%rZmEEOO&)T#MwhO;JjABS^d43+F-dB?RS= zkHbT-9ETAQB40)GLSFQ+yhlMl|O>InS2=P zX7U^qYcBUhyIaUlfXS6T(B?^|Hk7C=_G1uQ+ZyNQV>mOt2J_e~Mn9wr*^G0um)KJ% z;(j@@6MDyOTPvYKHzzuf;e13R$qs0l+{FbU%fsNxBu^%Xaxt1^k#9%-t#SntV3P+o zp`1tJJV^csT8I25!Vi{5s!wIIR1e>4$b7FM^Sy@5_Zl+aYsh@BA!~aL)4f=)<42k1 zAdF;+C>}eFqZAY0X~^16!*vSxKJmMmX(u$Mrl=~RRkjc3AvriV6(iLNt+Q_t(Ff;h zhb&gYck@XtGDifXyGN-TvRH=aCNd$XvDKhL{-CUQaAy92oOVV|lXDusLaI(UH$R0l z6YXPC8ppp+EZ_cD5?hPzOwCL>M(h=wo9T&|=|1Ettw|#r-QvTvu-+H95E`fD zJep)(7*~k$Zc7WbTQ-YhTh!s%SO&YPYk;QG<=L8?+z(FOXgktmO)(7uin$L5XAH*J zN^6?)WvX;829~u^B!5LxGR4yH%P_5i#xV+-EJoL)AXD~s#DtII2*S!@w{bFWyBgkv zwGDj^`9_D+!X5mhI;aV5G_KN!X&KiOPZk*k%7&nXWIwEWn&4}XDeC$rtd~0_6`iup zxr2<7$%wZ+_?CLL2}@|%9Mz|dc9PdP+6pD+%Z}g4l&my=Vb?HI)5dB^TCaA=Vezs( zO&y-Y3d8}zzhbG!W!wxgx^VM@6y06z+KjavJO&dPL!Ae|1f7=EHsTJlr1Dxn1rKNj zxYXo3H-b98a>xsktp!B70Qr z*MhZSr_?1aFp9ck;^f@4&ZZq~B;}BA_U!F2QF3lLXnL&FRZ?6lOhc#F;@CtK8M51u z1u6Pjj18y76iZZYfvtfRVF+^1i)T5Ub;_B&1Fd} zLhYK%;#28I+SZp$);C4zHvZDncxiP3j6HM%|EejB>gD@odJfXu^xAyQn7%or58HTM zud#)RSI8fJx4;C-H8rB2SeVvs@>u7>yp@SBPyE|9R~jCJnG&fiecLtyb+t7`{E2HR z9@ATMP&y>v6gv_FguZS&ucqd)XwT0ScbbWMN?Il2z(C|5wy_z;3vYS4DV@&wLxB(A zA&(xySog|qO2@Lfn!VK@{{+}tDPteWdcvfHGORMM%Gc&-#*cGBmza&!dM%s%*742f zsz>sWu?TeG4%tGBA5|`y=`7(q*a>p1aP^($5uV0iFb2`WK2Z(}wNsvUd4gIs!#7rP z1nt}CJB1+Xgvm1R1j;LqbDCark#$E?ynv(?m)Ev0vAQr=?vh*q*V zh^{WNzKEN{P#u;^=0=p%^{8+qd*>c4iU@yaG8pq{${CM_Ze4jes~8W5?p=92tC>jZ z%?Ekb9-YQ`+UWk2=j}1Z^TtOn<)VU#(TiR^>TRdhOWjHLQ605vDQ`Q_yPb46 zManpyE=gBIf0?ei>gVsFuiz?@dgz+oL6PVnkMx_~L6J1du8Vx^@;In}{t(?!;-JRd z6sXieZTVB6G6(g|ErF)snJP8u(de_7#_OkPVU+m8c$)44_rUdIyrz~ko_Wm`qUKE= z2Mt(7UlsU|_ST8vTI86jPH%&!-$SRXEAaqb9y49t+y;{_4zX9KY6}VGoYd+GOkL}Q zG};Nx6G5bEv0$`*rUx-Golf~0u^g7MhtW!F&lK|G7Nth}OF1$1BdIfbiD*h4)R5Z{ zsYi2?*%swnGf@)LfPdJ=PE?X?+lW?8kuo1f#LaF@)l7o@rjy9@KCEZW)993kM%%U6 zIv{Hnzt}z}O5Ncv<=F_WV;S)@*Flky6Y0h5nishmEtRv;_sYVs+sD`aDzz7a9g$uOF{+d8X(A{z5 zSsb+OCWReagPy~#g0=-uOi`c8XBnZ#u|8tkMGM-TDe9+0NoX9lYqYiwn4L+krA;oZ{|{hN5|jI;I^*d$uW-v? zwap|}f(~9jiDhQgkx3kd&Ck9=S<}xrn6Khsp+;GFK3cLS8zwWijIYz)$r-;UIs^B-o-L){5$M#%xc%sEK!ZSn}z@X?#_}=ijce0l4%)R zb;sT89uwRhn8_ODM_aIW#lMqi!!G2GcSF-#D8zb#mP>FjO`yYlba;dgx6EqlESBYY z7Qp^04z@a!4zeNopq+q0PC84V1O37)C>IC$Ln!3WaB$FXse&m7Cdz?{a$uqyn9f3B zqC}W3)7js2aAIXLQC>nQCHAHS3+V8djOE@=V>6Wc+&%!LmU{D)svl>ula?y^0k$mE zcK5_NbH+`cShc*I{mj%wt67>nd5+rsK^CPx_6X)h*%#PGf3lkW6*YP6zy34gst+Gw z#c}pYQ)i63YwE&@RrXidG`Z@&*V!v3HE%0Rt{T3TDYBaT4$D=`-eIY7$p-bIcUTKg zMSF}!!zT^rvJ@&+2k>nKufIE~7b(UG!;7}5Vl7AG`DYmCV%{CHTOf!fgO=maf(bx9aKbtVt{V zmsFX8rC)`U)4<={oD}@sL)4Bt*cjO|R()y*8lgY-*UOKj8G!Ol#N|!r?-Bff0G@sm zL-un5_zi-8B7olvo_bY0it%81);_e_k|IxZHzoo9ToaB0ueXUXpo#-`9o@i_AAJj; z`==-RWZ!VVfxV7LC<1pn8wDS@ciJWRz@6B)f)Cs#(zkU~ao{c%ms<({NmJ;*cFIFU zfxugV1%eN}VR=sQU(D7rRwOdFU+{tVX}<}cZUxnkt5}934J!weBBXxnm56Vm)X?25 zs!uNG;wA4^;ZBG`&by`BHyZM^`rx)+ny|7VKaT&;==akxj-K&HqQ!x~Cwzq4Y~jZb z`SDAByu^_1#3~#~eUN!lpu+QqOVSPLK8%3nn>}J;3 z(0|>!r(m?=ZB|nh>q!5Vyjt)sHZD{6|0+M1j&TpefFgE`(7d)5mA=4jcJbp8Zuyh2 z41Bs_Qr22ynS+ikvqbxgdp3-*2Y9EHAUVZc6qQH=sZ<=jCqXg1E@GG})c13rYk1lE zWm_-%q$sssY?$>QH}B*=j|*+!7q?fHw{30(xr$40Xtf5uh}D6#Y$&&(pD$8mJ-cbO zz9%Z5$8FYdo4sNT{v&MkTcS-|TP12~8jz8%xR!n%Oa23&Q3WaE5JN6MmhfX49pmoD z?w#y6chCYkA>vuXwUe>kcVeX~2*~o&q_u28Sw05wx)}NTO85#Ek);Y>S4jJKh?|8f zUs_^W20wP<$HDw~7e9LV@iBgUfgkts<9GacnT~N5+?r9iR|+)WUBrbgQH`i(v8;_I z254du7k%zU85%13Ra!(PJt%x#9Ii3?@Lw z>q6>(tVG*2t1r2*)W9>JD=lw{Is)J3i;U8)0PcXfmZEmwk9M}@BCZrx6k*_dstYtL zRTH~3aa0q(a1qC%wW5JP(F#?^9bmC7Q-wP4`Sd`+2fq6{T`N{0)K|o$A0>L}O~D8L zHs*a^j2+`5?xL{Le=p7d&w#eEYwO@sBa%+N%&qc@V(B@M7h3V`nD3~u4&~=YWG7l74U#6Lc&*8i#4^F zy;2favwY2}2v*dPz!!HWYgYGb;$hABC1D)+!@c)3tK*uu%7vPDm|4|Hhw)WMraJC0 zi`{vI#ah*oU*mt;IUVV$8ho5(%Ie^US!~tQ-(bopXWVYJH{Dki9Ag&sk#BLoJGch- z`x8&GrRwVyET+o!9e(sw)%ANkHi-Q9&6ct#K1u!a2R2cC=tnlN>gZPwV_z_JikH>MCqvX?A8QeL5Gw$Eboq6Nx(dAi zuV?6gMI%x@(RW_D4DPP3B0GIIb=Q6BzKd|TSa=t50^D6f zTlCGQF4w7jF0s6bRhsopAxjsowxfPYXMyIxZs>VrN{tQSEV=wF?p(=uOY54*@y_^ggE* z%2p4dj6>gQIbTxcE2vAqo~FK}_PD~j2Bp5FnI|ZNLZZ}-S6D_+CqWgchpw=8j{9HH zEH0O+j#_w_N{T=Dxd#rl+Dx9 #include @@ -328,7 +329,6 @@ char *investigation_project_create( char *database_path = NULL; GPtrArray *created_paths = NULL; - GError *error = NULL; if (!investigation_project_validate_create_parameters( parent_directory, @@ -460,29 +460,12 @@ char *investigation_project_create( return NULL; } - /* - * Pour ce ticket, on crée seulement un fichier vide. - * - * Le schéma SQLite sera ajouté dans un prochain ticket. - */ - if (!g_file_set_contents( + if (!database_initialize( database_path, - "", - 0, - &error + investigation_name, + investigation_path )) { - if (error != NULL) - { - g_warning( - "Impossible de créer '%s' : %s", - database_path, - error->message - ); - - g_clear_error(&error); - } - g_free(database_path); investigation_project_cleanup_created_paths( diff --git a/src/database/database.c b/src/database/database.c new file mode 100644 index 0000000..af750a8 --- /dev/null +++ b/src/database/database.c @@ -0,0 +1,621 @@ +/****************************************************************************** + * @file database.c + * @brief Initialisation de la base SQLite d'une enquête. + ******************************************************************************/ + +#include "database/database.h" + +#include +#include + +/** + * @brief Version actuelle du schéma SQLite. + */ +#define DATABASE_SCHEMA_VERSION "1" + +/** + * @brief Nom de l'application enregistré dans les métadonnées. + */ +#define DATABASE_APPLICATION_NAME "Labfy Investigation" + +/** + * @brief Crée la table contenant les métadonnées de l'enquête. + */ +static const char *const database_create_metadata_table_sql = + "CREATE TABLE metadata" + "(" + " key TEXT PRIMARY KEY," + " value TEXT NOT NULL" + ");"; + +/** + * @brief Crée la table représentant l'enquête courante. + */ +static const char *const database_create_investigation_table_sql = + "CREATE TABLE investigation" + "(" + " id INTEGER PRIMARY KEY CHECK (id = 1)," + " name TEXT NOT NULL," + " root_path TEXT NOT NULL," + " created_at TEXT NOT NULL" + ");"; + +/** + * @brief Requête d'insertion d'une métadonnée. + */ +static const char *const database_insert_metadata_sql = + "INSERT INTO metadata (key, value) VALUES (?, ?);"; + +/** + * @brief Requête d'insertion de l'enquête. + */ +static const char *const database_insert_investigation_sql = + "INSERT INTO investigation" + "(" + " id," + " name," + " root_path," + " created_at" + ")" + "VALUES" + "(" + " 1," + " ?," + " ?," + " ?" + ");"; + +/** + * @brief Vérifie les paramètres publics de l'initialisation. + */ +static bool database_validate_initialize_parameters( + const char *database_path, + const char *investigation_name, + const char *investigation_root_path +) +{ + if (database_path == NULL || database_path[0] == '\0') + { + return false; + } + + if (investigation_name == NULL || investigation_name[0] == '\0') + { + return false; + } + + if (investigation_root_path == NULL || + investigation_root_path[0] == '\0') + { + return false; + } + + return true; +} + +/** + * @brief Exécute une requête SQL statique. + * + * Cette fonction convient uniquement aux requêtes qui ne contiennent + * aucune donnée provenant de l'utilisateur. + */ +static bool database_execute_sql( + sqlite3 *database, + const char *sql +) +{ + char *error_message = NULL; + int result = SQLITE_ERROR; + + if (database == NULL || sql == NULL) + { + return false; + } + + result = sqlite3_exec( + database, + sql, + NULL, + NULL, + &error_message + ); + + if (result != SQLITE_OK) + { + g_warning( + "Erreur SQLite : %s", + error_message != NULL + ? error_message + : sqlite3_errmsg(database) + ); + + sqlite3_free(error_message); + + return false; + } + + sqlite3_free(error_message); + + return true; +} + +/** + * @brief Insère une paire clé-valeur dans la table metadata. + */ +static bool database_insert_metadata( + sqlite3 *database, + sqlite3_stmt *statement, + const char *key, + const char *value +) +{ + int result = SQLITE_ERROR; + + if (database == NULL || + statement == NULL || + key == NULL || + value == NULL) + { + return false; + } + + result = sqlite3_bind_text( + statement, + 1, + key, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de lier la clé '%s' : %s", + key, + sqlite3_errmsg(database) + ); + + return false; + } + + result = sqlite3_bind_text( + statement, + 2, + value, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de lier la valeur de '%s' : %s", + key, + sqlite3_errmsg(database) + ); + + return false; + } + + result = sqlite3_step(statement); + + if (result != SQLITE_DONE) + { + g_warning( + "Impossible d'insérer la métadonnée '%s' : %s", + key, + sqlite3_errmsg(database) + ); + + return false; + } + + result = sqlite3_reset(statement); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de réinitialiser la requête metadata : %s", + sqlite3_errmsg(database) + ); + + return false; + } + + result = sqlite3_clear_bindings(statement); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible d'effacer les paramètres metadata : %s", + sqlite3_errmsg(database) + ); + + return false; + } + + return true; +} + +/** + * @brief Insère toutes les métadonnées obligatoires. + */ +static bool database_insert_all_metadata( + sqlite3 *database, + const char *created_at, + const char *investigation_uuid +) +{ + sqlite3_stmt *statement = NULL; + int result = SQLITE_ERROR; + bool success = false; + + if (database == NULL || + created_at == NULL || + investigation_uuid == NULL) + { + return false; + } + + result = sqlite3_prepare_v2( + database, + database_insert_metadata_sql, + -1, + &statement, + NULL + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de préparer l'insertion des métadonnées : %s", + sqlite3_errmsg(database) + ); + + return false; + } + + success = + database_insert_metadata( + database, + statement, + "schema_version", + DATABASE_SCHEMA_VERSION + ) && + database_insert_metadata( + database, + statement, + "application", + DATABASE_APPLICATION_NAME + ) && + database_insert_metadata( + database, + statement, + "created_at", + created_at + ) && + database_insert_metadata( + database, + statement, + "investigation_uuid", + investigation_uuid + ); + + if (sqlite3_finalize(statement) != SQLITE_OK) + { + g_warning( + "Impossible de finaliser la requête metadata : %s", + sqlite3_errmsg(database) + ); + + return false; + } + + return success; +} + +/** + * @brief Insère la ligne représentant l'enquête courante. + */ +static bool database_insert_investigation( + sqlite3 *database, + const char *investigation_name, + const char *investigation_root_path, + const char *created_at +) +{ + sqlite3_stmt *statement = NULL; + int result = SQLITE_ERROR; + bool success = false; + + if (database == NULL || + investigation_name == NULL || + investigation_root_path == NULL || + created_at == NULL) + { + return false; + } + + result = sqlite3_prepare_v2( + database, + database_insert_investigation_sql, + -1, + &statement, + NULL + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de préparer l'insertion de l'enquête : %s", + sqlite3_errmsg(database) + ); + + return false; + } + + result = sqlite3_bind_text( + statement, + 1, + investigation_name, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + goto cleanup; + } + + result = sqlite3_bind_text( + statement, + 2, + investigation_root_path, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + goto cleanup; + } + + result = sqlite3_bind_text( + statement, + 3, + created_at, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + goto cleanup; + } + + result = sqlite3_step(statement); + + if (result != SQLITE_DONE) + { + goto cleanup; + } + + success = true; + +cleanup: + + if (!success) + { + g_warning( + "Impossible d'insérer l'enquête : %s", + sqlite3_errmsg(database) + ); + } + + if (sqlite3_finalize(statement) != SQLITE_OK) + { + g_warning( + "Impossible de finaliser la requête investigation : %s", + sqlite3_errmsg(database) + ); + + success = false; + } + + return success; +} + +/** + * @brief Produit une date UTC au format ISO 8601 attendu. + * + * @return Une nouvelle chaîne à libérer avec g_free(), ou NULL. + */ +static char *database_create_utc_timestamp(void) +{ + GDateTime *date_time = NULL; + char *timestamp = NULL; + + date_time = g_date_time_new_now_utc(); + + if (date_time == NULL) + { + return NULL; + } + + timestamp = g_date_time_format( + date_time, + "%Y-%m-%dT%H:%M:%SZ" + ); + + g_date_time_unref(date_time); + + return timestamp; +} + +bool database_initialize( + const char *database_path, + const char *investigation_name, + const char *investigation_root_path +) +{ + sqlite3 *database = NULL; + char *created_at = NULL; + char *investigation_uuid = NULL; + + int result = SQLITE_ERROR; + bool transaction_started = false; + bool success = false; + + if (!database_validate_initialize_parameters( + database_path, + investigation_name, + investigation_root_path + )) + { + return false; + } + + created_at = database_create_utc_timestamp(); + + if (created_at == NULL) + { + return false; + } + + investigation_uuid = g_uuid_string_random(); + + if (investigation_uuid == NULL) + { + g_free(created_at); + return false; + } + + result = sqlite3_open_v2( + database_path, + &database, + SQLITE_OPEN_READWRITE | + SQLITE_OPEN_CREATE | + SQLITE_OPEN_PRIVATECACHE, + NULL + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible d'ouvrir la base '%s' : %s", + database_path, + database != NULL + ? sqlite3_errmsg(database) + : sqlite3_errstr(result) + ); + + goto cleanup; + } + + if (!database_execute_sql( + database, + "PRAGMA foreign_keys = ON;" + )) + { + goto cleanup; + } + + if (!database_execute_sql( + database, + "BEGIN IMMEDIATE;" + )) + { + goto cleanup; + } + + transaction_started = true; + + if (!database_execute_sql( + database, + database_create_metadata_table_sql + )) + { + goto rollback; + } + + if (!database_execute_sql( + database, + database_create_investigation_table_sql + )) + { + goto rollback; + } + + if (!database_insert_all_metadata( + database, + created_at, + investigation_uuid + )) + { + goto rollback; + } + + if (!database_insert_investigation( + database, + investigation_name, + investigation_root_path, + created_at + )) + { + goto rollback; + } + + if (!database_execute_sql( + database, + "COMMIT;" + )) + { + goto rollback; + } + + transaction_started = false; + success = true; + + goto cleanup; + +rollback: + + if (transaction_started) + { + if (!database_execute_sql( + database, + "ROLLBACK;" + )) + { + g_warning( + "Échec du rollback de la base '%s'.", + database_path + ); + } + + transaction_started = false; + } + +cleanup: + + if (database != NULL) + { + result = sqlite3_close(database); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de fermer proprement la base '%s' : %s", + database_path, + sqlite3_errstr(result) + ); + + success = false; + } + } + + g_free(investigation_uuid); + g_free(created_at); + + return success; +} diff --git a/tests/test_database b/tests/test_database new file mode 100755 index 0000000000000000000000000000000000000000..bad3911669e2822fe9f4e0cffa68ac982991e7ec GIT binary patch literal 26856 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 literal 0 HcmV?d00001 diff --git a/tests/test_database.c b/tests/test_database.c new file mode 100644 index 0000000..b227363 --- /dev/null +++ b/tests/test_database.c @@ -0,0 +1,339 @@ +/****************************************************************************** + * @file test_database.c + * @brief Tests d'intégration du module Database. + ******************************************************************************/ + +#include "database/database.h" + +#include +#include +#include + +#include +#include +#include + +/** + * @brief Lit une valeur unique retournée par une requête SQL. + * + * @param database Base SQLite ouverte. + * @param sql Requête retournant une seule colonne et une seule ligne. + * + * @return Une nouvelle chaîne à libérer avec g_free(), ou NULL. + */ +static char *test_database_read_single_text( + sqlite3 *database, + const char *sql +) +{ + sqlite3_stmt *statement = NULL; + const unsigned char *text = NULL; + char *result_text = NULL; + int result = SQLITE_ERROR; + + assert(database != NULL); + assert(sql != NULL); + + result = sqlite3_prepare_v2( + database, + sql, + -1, + &statement, + NULL + ); + + assert(result == SQLITE_OK); + assert(statement != NULL); + + result = sqlite3_step(statement); + + assert(result == SQLITE_ROW); + + text = sqlite3_column_text( + statement, + 0 + ); + + assert(text != NULL); + + result_text = g_strdup( + (const char *)text + ); + + assert(result_text != NULL); + + result = sqlite3_finalize(statement); + + assert(result == SQLITE_OK); + + return result_text; +} + +/** + * @brief Vérifie qu'une table existe. + */ +static void test_database_assert_table_exists( + sqlite3 *database, + const char *table_name +) +{ + sqlite3_stmt *statement = NULL; + int result = SQLITE_ERROR; + + assert(database != NULL); + assert(table_name != NULL); + + result = sqlite3_prepare_v2( + database, + "SELECT COUNT(*) " + "FROM sqlite_master " + "WHERE type = 'table' AND name = ?;", + -1, + &statement, + NULL + ); + + assert(result == SQLITE_OK); + assert(statement != NULL); + + result = sqlite3_bind_text( + statement, + 1, + table_name, + -1, + SQLITE_TRANSIENT + ); + + assert(result == SQLITE_OK); + + result = sqlite3_step(statement); + + assert(result == SQLITE_ROW); + assert(sqlite3_column_int(statement, 0) == 1); + + result = sqlite3_finalize(statement); + + assert(result == SQLITE_OK); +} + +/** + * @brief Vérifie l'initialisation complète d'une base. + */ +static void test_database_initialize_valid_database(void) +{ + char *temporary_directory = NULL; + char *database_path = NULL; + + char *schema_version = NULL; + char *application_name = NULL; + char *created_at = NULL; + char *investigation_uuid = NULL; + char *investigation_name = NULL; + char *investigation_root_path = NULL; + char *investigation_created_at = NULL; + + sqlite3 *database = NULL; + GError *error = NULL; + int result = SQLITE_ERROR; + + temporary_directory = g_dir_make_tmp( + "labfy-database-test-XXXXXX", + &error + ); + + assert(temporary_directory != NULL); + assert(error == NULL); + + database_path = g_build_filename( + temporary_directory, + "Enquete.sqlite", + NULL + ); + + assert(database_path != NULL); + + assert( + database_initialize( + database_path, + "Enquete_Test", + temporary_directory + ) + ); + + assert( + g_file_test( + database_path, + G_FILE_TEST_IS_REGULAR + ) + ); + + result = sqlite3_open_v2( + database_path, + &database, + SQLITE_OPEN_READONLY, + NULL + ); + + assert(result == SQLITE_OK); + assert(database != NULL); + + test_database_assert_table_exists( + database, + "metadata" + ); + + test_database_assert_table_exists( + database, + "investigation" + ); + + schema_version = test_database_read_single_text( + database, + "SELECT value FROM metadata " + "WHERE key = 'schema_version';" + ); + + application_name = test_database_read_single_text( + database, + "SELECT value FROM metadata " + "WHERE key = 'application';" + ); + + created_at = test_database_read_single_text( + database, + "SELECT value FROM metadata " + "WHERE key = 'created_at';" + ); + + investigation_uuid = test_database_read_single_text( + database, + "SELECT value FROM metadata " + "WHERE key = 'investigation_uuid';" + ); + + investigation_name = test_database_read_single_text( + database, + "SELECT name FROM investigation " + "WHERE id = 1;" + ); + + investigation_root_path = test_database_read_single_text( + database, + "SELECT root_path FROM investigation " + "WHERE id = 1;" + ); + + investigation_created_at = test_database_read_single_text( + database, + "SELECT created_at FROM investigation " + "WHERE id = 1;" + ); + + assert(strcmp(schema_version, "1") == 0); + assert(strcmp(application_name, "Labfy Investigation") == 0); + + assert(created_at[0] != '\0'); + assert(investigation_uuid[0] != '\0'); + + assert(strcmp(investigation_name, "Enquete_Test") == 0); + assert(strcmp(investigation_root_path, temporary_directory) == 0); + assert(strcmp(investigation_created_at, created_at) == 0); + + result = sqlite3_close(database); + + assert(result == SQLITE_OK); + + assert(g_remove(database_path) == 0); + assert(g_rmdir(temporary_directory) == 0); + + g_free(investigation_created_at); + g_free(investigation_root_path); + g_free(investigation_name); + g_free(investigation_uuid); + g_free(created_at); + g_free(application_name); + g_free(schema_version); + g_free(database_path); + g_free(temporary_directory); +} + +/** + * @brief Vérifie le refus des paramètres invalides. + */ +static void test_database_initialize_invalid_parameters(void) +{ + assert( + !database_initialize( + NULL, + "Enquete", + "/tmp/Enquete" + ) + ); + + assert( + !database_initialize( + "", + "Enquete", + "/tmp/Enquete" + ) + ); + + assert( + !database_initialize( + "/tmp/Enquete.sqlite", + NULL, + "/tmp/Enquete" + ) + ); + + assert( + !database_initialize( + "/tmp/Enquete.sqlite", + "", + "/tmp/Enquete" + ) + ); + + assert( + !database_initialize( + "/tmp/Enquete.sqlite", + "Enquete", + NULL + ) + ); + + assert( + !database_initialize( + "/tmp/Enquete.sqlite", + "Enquete", + "" + ) + ); +} + +/** + * @brief Vérifie l'échec sur un chemin dont le parent n'existe pas. + */ +static void test_database_initialize_missing_parent(void) +{ + assert( + !database_initialize( + "/tmp/labfy-missing-parent/database/Enquete.sqlite", + "Enquete", + "/tmp/labfy-missing-parent" + ) + ); +} + +int main(void) +{ + test_database_initialize_valid_database(); + test_database_initialize_invalid_parameters(); + test_database_initialize_missing_parent(); + + printf( + "Database : tous les tests sont valides.\n" + ); + + return 0; +} diff --git a/tests/test_investigation_project b/tests/test_investigation_project index 6fdc14da7553d9b3541418c1f18276bc8156a6b6..6bca42ee51eb845c72aec687ce4f7186e6d07189 100755 GIT binary patch literal 45072 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 #include #include +#include #include #include @@ -274,6 +275,43 @@ static void test_create_valid_investigation(void) ) == NULL ); + sqlite3 *database = NULL; + sqlite3_stmt *statement = NULL; + int result = SQLITE_ERROR; + + result = sqlite3_open_v2( + database_path, + &database, + SQLITE_OPEN_READONLY, + NULL + ); + + assert(result == SQLITE_OK); + + result = sqlite3_prepare_v2( + database, + "SELECT COUNT(*) " + "FROM sqlite_master " + "WHERE type='table' " + "AND name='metadata';", + -1, + &statement, + NULL + ); + + assert(result == SQLITE_OK); + + result = sqlite3_step(statement); + + assert(result == SQLITE_ROW); + assert(sqlite3_column_int(statement, 0) == 1); + + result = sqlite3_finalize(statement); + assert(result == SQLITE_OK); + + result = sqlite3_close(database); + assert(result == SQLITE_OK); + /* * Nettoyage complet. */