From 40d9d27ba6cfd1c7629aaede3966a23b980bda49 Mon Sep 17 00:00:00 2001 From: fy59 Date: Fri, 28 Aug 2026 06:31:44 +0200 Subject: [PATCH] secure commit --- README.md | 15 +- docs/architecture/project_database.md | 74 ++- docs/architecture/task_system.md | 9 + docs/roadmap/roadmap.md | 65 ++- include/lardon3d/acquisition_campaign_task.h | 63 ++ include/lardon3d/project.h | 3 + include/lardon3d/project_db.h | 30 +- meson.build | 20 + src/acquisition_campaign_task.cpp | 569 +++++++++++++++++++ src/project.c | 36 +- src/project_db.c | 231 +++++++- src/task_kinds.c | 10 + tests/test_acquisition_campaign_task.cpp | 187 ++++++ tests/test_geometric_verification.c | 8 +- tests/test_geometric_verifier_task.c | 2 +- tests/test_match_result.c | 10 +- tests/test_matcher_task.c | 2 +- tests/test_project_db.c | 55 +- tests/test_sparse_sfm_model.c | 2 +- tests/test_sparse_sfm_payload.cpp | 2 +- 20 files changed, 1290 insertions(+), 103 deletions(-) create mode 100644 include/lardon3d/acquisition_campaign_task.h create mode 100644 src/acquisition_campaign_task.cpp create mode 100644 tests/test_acquisition_campaign_task.cpp diff --git a/README.md b/README.md index 64c361b..20bf037 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,12 @@ persistante, enrichissable et versionnable. - **Image View** : vues triées et filtrées pour la TUI - **Task** : moteur de tâches avec pause/reprise, annulation et séquences - **Task Checkpoint v1** : snapshot durable, fichier atomique et reprise sûre -- **Project Database v19** : fondations v18 préservées, avec Capture / Asset - Provenance additive +- **Project Database v20** : fondations v19 préservées, avec persistance + additive des tâches de campagne d'acquisition +- **Exécution durable de campagne d'acquisition** : tâche générique à requête + typée immuable, confirmations `CALLER_EXPLICIT`, curseur et correspondance + tâche/groupe→Capture persistants ; un groupe S3-E par séquence, reprise par + la registry, la Queue et le Resource Governor existants - **Sparse SfM Gates C/D/E** : géométrie calibrée, noyau incrémental et Bundle Adjustment final par composante, tous PASS / FROZEN - **Sparse SfM Gate F** : orchestration durable, runtime gouverné et publication @@ -62,15 +66,14 @@ persistante, enrichissable et versionnable. ### Prochaine tranche -- **Exécution durable de campagne d'acquisition** : confirmations et progression - persistantes, matérialisation S3-E incrémentale, reprise par `capture_id` retenu - et admission par les Task/Scheduler/Governor existants. Voir la +- **Intégration multi-campagne réelle A6000 + S21 Engine Bay** : exécuter les + campagnes persistantes à travers le pipeline scientifique existant. Voir la [roadmap canonique](docs/roadmap/roadmap.md). ### Plus tard / différé - publication durable dense/mesh et scratch SSD externe optionnel gouverné ; -- workflow TUI de confirmation/progression et sources mixtes multi-ScanSet ; +- workflow TUI de confirmation/progression ; - vidéo/keyframes et **Capture Guidance / Live Coverage** : analyse et viewer de couverture, suggestions de prises de vue puis assistance live, après reconstruction mature ; diff --git a/docs/architecture/project_database.md b/docs/architecture/project_database.md index 8f03e00..0b48eea 100644 --- a/docs/architecture/project_database.md +++ b/docs/architecture/project_database.md @@ -1,6 +1,6 @@ # Base de données projet Lardon3D -## Capture / Asset Provenance v1 — Project DB v19 +## Capture / Asset Provenance v1 — Project DB v19 (historique FROZEN) **PASS / FROZEN.** La migration transactionnelle v18→v19 ajoute seulement une fondation de catalogue : `captures`, `capture_images`, @@ -109,10 +109,57 @@ confirmation explicite `CALLER_EXPLICIT` avant tout regroupement. Le fixture d'intégration couvre l'adaptateur, sa matérialisation et son retry ; aucune mutation de campagne complète n'a été effectuée. -Restent hors S3 : identité persistante de campagne ou de Task, exécution par -scheduler/Governor, traitement de campagne complet, TUI, scrub d'assets et -réconciliation des orphelins, ainsi que vidéo et reconstruction aval lorsqu'ils -sont applicables. +Restent hors S3 : TUI, scrub d'assets et réconciliation des orphelins, ainsi +que vidéo et reconstruction aval lorsqu'ils sont applicables. L'identité +opérationnelle persistante de campagne, son exécution par Task/Scheduler/Governor +et sa reprise sont définies par Project DB v20 ci-dessous ; ils ne modifient pas +le contrat S3. + +## Exécution durable de campagne d'acquisition — Project DB v20 + +**PASS / FROZEN.** La migration transactionnelle additive +v19→v20 ajoute `acquisition_campaign_tasks` et +`acquisition_campaign_captures`, sans modifier les tables ni les identités +Capture/Asset de v19. Une base v19 est donc ouverte par la chaîne séquentielle +existante ; un échec de migration laisse la v19 complète et utilisable. + +`acquisition_campaign_tasks` est lié une-à-une à la tâche générique par +`task_id`, qui est l'identité opérationnelle de la campagne. La création et les +checkpoints enregistrent dans une même transaction le snapshot Task générique, +sa référence de checkpoint et le record typé de campagne. Ce record conserve le +`scanset_id`, le curseur `next_group_id`, le nombre de groupes et une requête +immuable : un upsert ne peut faire progresser le curseur que si ScanSet, nombre +de groupes et octets de requête sont identiques. + +La requête v1 est un codec borné, déterministe, à magic/version explicites et +champs entiers de largeur fixe little-endian. Elle sérialise les sources, leurs +métadonnées déjà validées, les confirmations et les options d'ingestion ; elle +est refusée si ses bornes, longueurs, énumérations, indices, plan ou octets de +fin ne sont pas valides. Les confirmations sont ainsi persistées explicitement +et ne confèrent que la basis `CALLER_EXPLICIT`, jamais une inférence `STRONG`. + +Les groupes de plan gardent leurs IDs un-based. Pour chaque groupe achevé, +`acquisition_campaign_captures` conserve la relation unique +`(task_id, group_id) → capture_id` et interdit qu'un même Capture corresponde à +deux groupes de la même tâche. Après le retour de S3-E, une transaction unique +retient cette relation et avance `next_group_id`, avant la progression générique +et le checkpoint. La reprise peut alors transmettre le `capture_id` retenu à +S3-E, sans réhoming ni inférence depuis un chemin, SHA, basename, timestamp, +métadonnée ou `image_id`. + +Chaque séquence matérialise un seul groupe. Pause et annulation restent +coopératives aux frontières de groupe ; après un groupe non terminal, +`sequence_break` rend la réservation et force la réadmission par la Queue et le +Resource Governor existants. À l'ouverture, la registry existante reconstruit +la tâche depuis le Task ID et la requête durable, puis le mécanisme de reprise +existant la soumet à la Queue. Cette tranche n'introduit aucun runtime, queue, +governor, scheduler ou stockage parallèle et reste générique pour les sources +mono-source comme multi-source. + +La fenêtre résiduelle acceptée est strictement l'arrêt après le retour de S3-E +et avant la transaction de rétention du Capture. Aucun `capture_id` n'est alors +inféré et aucune garantie exactly-once ne lui est attribuée ; les autres +frontières s'appuient sur la relation retenue et le curseur transactionnel. ### S3-D — Acquisition Pairing Evidence v1 @@ -201,8 +248,10 @@ persistée exactement ; seule une composante de graphe non reconstruite est omise. Les comptes et métriques globaux décrivent exclusivement la géométrie effectivement persistée. Aucun placeholder ni table diagnostique n'est ajouté. -> Version courante : **v19**. La migration transactionnelle v18→v19 ajoute la -> fondation Capture/Asset Provenance décrite ci-dessus sans modifier les tables +> Version courante : **v20**. La migration transactionnelle additive v19→v20 +> ajoute les records de campagne durable décrits ci-dessus sans modifier les +> tables ni identités Capture/Asset de v19. La migration transactionnelle +> v18→v19 ajoute la fondation Capture/Asset Provenance sans modifier les tables > scientifiques historiques. La migration transactionnelle v17→v18 ajoute le > discriminateur générique nullable et les deux tables H minimales décrites > ci-dessus. La migration v16→v17 ajoute le @@ -946,10 +995,17 @@ ouvert. ## Statut -**PASS / FROZEN** — SQLite système, schéma v19 et migrations séquentielles -v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11→v12→v13→v14→v15→v16→v17→v18→v19, identité projet, transactions +**PASS / FROZEN** — SQLite système, schéma v20 et migrations séquentielles +v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11→v12→v13→v14→v15→v16→v17→v18→v19→v20, identité projet, transactions tâche+checkpoint, pagination de reprise et artefacts génériques. +**PASS / FROZEN** — Project DB v20 : migration additive +v19→v20, record typé de campagne lié au Task ID, requête immuable bornée, +confirmations `CALLER_EXPLICIT`, curseur et relation unique +task/groupe→Capture. La reprise reste celle de la registry, de la Queue et du +Resource Governor existants ; aucune identité de Capture n'est inférée dans la +fenêtre résiduelle pré-rétention. + **IMPLEMENTED** — ouverture/fermeture avec le projet, identité INI/DB cohérente, publication de checkpoints par le projet et inventaire de reprise validé. diff --git a/docs/architecture/task_system.md b/docs/architecture/task_system.md index ecffd96..db5af70 100644 --- a/docs/architecture/task_system.md +++ b/docs/architecture/task_system.md @@ -122,6 +122,15 @@ complet. Le scope GVR est persistant et immuable ; la reprise rejoue depuis le début avant publication et l'exact reuse Gate C absorbe un crash post-publication. Pause et annulation sont observées avant/after l'unité Gate B non préemptible. +**PASS / FROZEN** — `acquisition_campaign.run` v1 est une +tâche durable reconstruite par la registry existante à partir de son Task ID et +de sa requête typée immuable Project DB v20. Elle matérialise un seul groupe +S3-E par séquence, vérifie pause/annulation aux frontières de groupe, retient +transactionnellement Capture et curseur avant progression/checkpoint, puis +appelle `sequence_break` avant le groupe suivant. Sa reprise passe par la Queue +et le Resource Governor existants ; aucune boucle d'exécution parallèle n'est +introduite. + Le chemin de production de l'import ne possède plus de thread ni de drapeau d'annulation privés. Son wrapper TUI ne fait qu'enqueue/cancel/observer la tâche générique. Chaque callback traite un lot borné, checkpoint hors mutex de diff --git a/docs/roadmap/roadmap.md b/docs/roadmap/roadmap.md index 70e81e7..bd134b9 100644 --- a/docs/roadmap/roadmap.md +++ b/docs/roadmap/roadmap.md @@ -74,7 +74,7 @@ Le stem commun ne prouve jamais une acquisition. Les 953 propositions doivent être confirmées et deviennent `CALLER_EXPLICIT`, jamais `STRONG`. La campagne complète n'a pas encore été matérialisée. -### Limite de reprise DB v19 +### Limite de reprise DB v19 (historique) S3-E reprend une matérialisation connue par `resume_capture_id`. Une campagne peut reconstruire son plan et conserver côté appelant le `capture_id` de chaque @@ -85,31 +85,41 @@ future identité durable de requête/campagne pourra fermer cette fenêtre ; ell ne sera jamais déduite d'un chemin, SHA, basename, timestamp, métadonnée ou `image_id`. -## NEXT — exécution durable de campagne d'acquisition +## Exécution durable de campagne d'acquisition — PASS / FROZEN -La prochaine tranche architecturale unique est l'exécution opérationnelle du -plan S3 gelé en réutilisant Task, Scheduler et Governor existants : +Project DB v20 apporte une migration additive v19→v20. Une tâche de campagne +enregistre atomiquement son snapshot générique, sa référence de checkpoint et +sa requête typée immuable. Le Task ID est l'identité opérationnelle de la +campagne ; la requête ne déduit aucune identité depuis les sources. Son codec +v1 est borné, à champs de largeur fixe et little-endian ; il conserve les +sources, les confirmations explicites et les options d'ingestion. -1. identité durable de requête et payload Task borné ; -2. persistance/rechargement des confirmations, du curseur et des `capture_id` ; -3. matérialisation incrémentale d'un groupe S3-E par unité sûre ; -4. checkpoint après rétention durable, pause/reprise/annulation aux frontières ; -5. admission et budgets par l'unique Resource Governor ; -6. progression et échecs sans runtime, queue ou persistence parallèle. +La requête persiste les confirmations comme `CALLER_EXPLICIT`, jamais comme +inférence `STRONG`. Le plan conserve des IDs de groupe un-based et Project DB +persiste la correspondance `(task_id, group_id) → capture_id`, ainsi que le +curseur. Chaque séquence admet et matérialise exactement un groupe via S3-E. +Après le retour de S3-E, une transaction conserve le Capture et avance le +curseur avant la progression générique et le checkpoint. Pause et annulation +sont coopératives aux frontières de groupe ; entre deux groupes, +`sequence_break` rend la réservation et impose une nouvelle admission. -Cette tranche décidera explicitement si une évolution additive après DB v19 est -nécessaire. Elle ne peut ni changer les identités S3 ni fusionner des Captures. +À la réouverture, la registry existante reconstruit la tâche à partir de son +Task ID et de sa requête, puis la Queue et le Governor existants la réadmettent. +Aucun runtime, queue, governor ou mécanisme de persistance parallèle n'est +introduit. La fenêtre résiduelle acceptée demeure l'arrêt après le retour de +S3-E et avant la transaction de rétention : elle ne déclenche aucune inférence +de Capture et ne fournit pas de garantie exactly-once pour ce groupe. -## NEXT REAL-DATA MILESTONE — A6000 ENGINE BAY END-TO-END +## NEXT REAL-DATA MILESTONE — A6000 + S21 ENGINE BAY MULTI-CAMPAIGN INTEGRATION -1. confirmer explicitement les 953 propositions ARW/JPEG ; -2. matérialiser progressivement environ 953 Captures ; -3. produire les représentations scientifiques par lots gouvernés ; -4. exécuter features, candidats, matching, vérification, tracks et Sparse SfM ; -5. évaluer la qualité de la reconstruction sparse ; -6. exécuter MVS/dense avec budgets et scratch contrôlés ; -7. publier durablement nuage dense et mesh ; -8. comparer le résultat aux tentatives photogrammétriques antérieures. +1. confirmer explicitement et exécuter les campagnes A6000 et S21 ; +2. matérialiser progressivement leurs Captures et représentations scientifiques + par lots gouvernés ; +3. exécuter features, candidats, matching, vérification, tracks et Sparse SfM ; +4. évaluer la qualité de la reconstruction sparse multi-campagne ; +5. exécuter MVS/dense avec budgets et scratch contrôlés ; +6. publier durablement nuage dense et mesh ; +7. comparer le résultat aux tentatives photogrammétriques antérieures. Ce milestone est une intégration réelle, pas une nouvelle série de micro-gates S3. Les antécédents d'OOM/SIGSEGV OpenMVS, pression swap, grands intermédiaires @@ -204,7 +214,8 @@ Le but à long terme n'est pas seulement de reconstruire ce qui a été photographié, mais de guider activement l'opérateur vers les photographies qui manquent encore pour obtenir une reconstruction fiable. Cette capacité est postérieure à une reconstruction suffisamment mature ; elle ne fait pas partie -de S3 et ne remplace pas la prochaine tranche d'exécution durable de campagne. +de S3 et ne remplace pas le prochain milestone d'intégration multi-campagne +A6000 + S21. ```text photographies existantes @@ -421,10 +432,8 @@ L'ordre demeure sans ambiguïté : ```text CURRENT NEXT - exécution durable de campagne (Task / Queue / Scheduler / Governor, - confirmations durables, progression et reprise) -→ INTÉGRATION RÉELLE - campagnes A6000 + S21 → pipeline scientifique → dense/mesh → publication + intégration réelle multi-campagne A6000 + S21 + → pipeline scientifique → dense/mesh → publication → LATER Coverage Analysis → Coverage Viewer → suggestions de points de vue → localisation live → intégration HDMI/USB → Capture Guidance / Live Coverage @@ -448,8 +457,8 @@ scientifique. - ALIKED tant que provenance modèle, export ONNX et oracle upstream ne sont pas reproductibles. -Ces idées ne doivent pas devancer l'exécution durable de campagne ni créer un -second runtime, Governor ou système de persistance. +Ces idées ne doivent pas devancer l'intégration réelle multi-campagne ni créer +un second runtime, Governor ou système de persistance. ## Principes de séquencement diff --git a/include/lardon3d/acquisition_campaign_task.h b/include/lardon3d/acquisition_campaign_task.h new file mode 100644 index 0000000..e7ef7a4 --- /dev/null +++ b/include/lardon3d/acquisition_campaign_task.h @@ -0,0 +1,63 @@ +#ifndef LARDON3D_ACQUISITION_CAMPAIGN_TASK_H +#define LARDON3D_ACQUISITION_CAMPAIGN_TASK_H + +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LARDON3D_ACQUISITION_CAMPAIGN_TASK_KIND "acquisition_campaign.run" +enum { + LARDON3D_ACQUISITION_CAMPAIGN_TASK_KIND_VERSION = 1, + LARDON3D_ACQUISITION_CAMPAIGN_REQUEST_VERSION = 1, +}; + +/* Derived solely from the frozen source, path, confirmation and group bounds. + */ +#define LARDON3D_ACQUISITION_CAMPAIGN_TASK_REQUEST_MAX_BYTES \ + ((size_t)LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES * \ + (LARDON3D_ACQUISITION_CAMPAIGN_PATH_CAPACITY + 700u) + \ + (size_t)LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES * \ + (4u + 4u * LARDON3D_ACQUISITION_INGEST_MAX_SOURCES) + \ + 128u) + +typedef struct { + const Lardon3DAcquisitionCampaignSource *sources; + size_t source_count; + const Lardon3DAcquisitionCampaignConfirmation *confirmations; + size_t confirmation_count; + Lardon3DAcquisitionIngestOptions ingest_options; +} Lardon3DAcquisitionCampaignTaskRequest; + +Lardon3DTask *lardon3d_project_create_acquisition_campaign_task( + Lardon3DAppState *state, uint64_t scanset_id, + const Lardon3DAcquisitionCampaignTaskRequest *request, uint64_t *task_id); +bool lardon3d_project_enqueue_acquisition_campaign( + Lardon3DAppState *state, uint64_t scanset_id, + const Lardon3DAcquisitionCampaignTaskRequest *request, uint64_t *task_id); +bool lardon3d_acquisition_campaign_task_reconstruct( + const Lardon3DTaskDurableSnapshot *snapshot, void *context, + Lardon3DTaskKindBinding *binding); + +/* Exposed for deterministic codec validation without executing ingestion. */ +bool lardon3d_acquisition_campaign_request_encode( + const Lardon3DAcquisitionCampaignTaskRequest *request, + unsigned char *output, size_t capacity, size_t *size); +bool lardon3d_acquisition_campaign_request_decode( + const unsigned char *input, size_t size, + Lardon3DAcquisitionCampaignSource *sources, size_t source_capacity, + Lardon3DAcquisitionCampaignConfirmation *confirmations, + size_t confirmation_capacity, + Lardon3DAcquisitionCampaignTaskRequest *request); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/lardon3d/project.h b/include/lardon3d/project.h index 45fd892..d20a4e2 100644 --- a/include/lardon3d/project.h +++ b/include/lardon3d/project.h @@ -58,6 +58,9 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_task(Lardon3DApp Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_image_import_task(Lardon3DAppState *state, const Lardon3DTask *task, const char *source_path, uint64_t scanset_id); +Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_acquisition_campaign_task( + Lardon3DAppState *state, const Lardon3DTask *task, + const Lardon3DProjectDbAcquisitionCampaignTask *parameters); Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_feature_extract_task( Lardon3DAppState *state, const Lardon3DTask *task, const Lardon3DProjectDbFeatureExtractTask *parameters); diff --git a/include/lardon3d/project_db.h b/include/lardon3d/project_db.h index 9409bce..a66bd5a 100644 --- a/include/lardon3d/project_db.h +++ b/include/lardon3d/project_db.h @@ -10,7 +10,7 @@ #include enum { - LARDON3D_PROJECT_DB_SCHEMA_VERSION = 19, + LARDON3D_PROJECT_DB_SCHEMA_VERSION = 20, LARDON3D_PROJECT_DB_ID_CAPACITY = 65, LARDON3D_PROJECT_DB_KIND_CAPACITY = 65, LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096, @@ -100,6 +100,20 @@ typedef struct { uint64_t scanset_id; } Lardon3DProjectDbImageImport; +typedef struct { + uint64_t task_id; + uint64_t scanset_id; + uint32_t next_group_id; + uint32_t group_count; + const unsigned char *request; + size_t request_size; +} Lardon3DProjectDbAcquisitionCampaignTask; + +typedef struct { + uint32_t group_id; + uint64_t capture_id; +} Lardon3DProjectDbAcquisitionCampaignCapture; + typedef struct { uint64_t scanset_id; char name[LARDON3D_PROJECT_DB_SCANSET_NAME_CAPACITY]; @@ -570,6 +584,20 @@ Lardon3DProjectDbResult lardon3d_project_db_list_candidate_pairs( Lardon3DProjectDbResult lardon3d_project_db_load_image_import(Lardon3DProjectDb *database, uint64_t task_id, Lardon3DProjectDbImageImport *parameters); +Lardon3DProjectDbResult lardon3d_project_db_record_acquisition_campaign_task( + Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot, + const char *task_kind, uint32_t task_kind_version, + const Lardon3DProjectDbCheckpoint *checkpoint, + const Lardon3DProjectDbAcquisitionCampaignTask *parameters, int64_t updated_at); +Lardon3DProjectDbResult lardon3d_project_db_load_acquisition_campaign_task( + Lardon3DProjectDb *database, uint64_t task_id, unsigned char *request, + size_t request_capacity, Lardon3DProjectDbAcquisitionCampaignTask *parameters); +Lardon3DProjectDbResult lardon3d_project_db_retain_acquisition_campaign_capture( + Lardon3DProjectDb *database, uint64_t task_id, uint32_t group_id, + uint64_t capture_id, uint32_t next_group_id); +Lardon3DProjectDbResult lardon3d_project_db_load_acquisition_campaign_capture( + Lardon3DProjectDb *database, uint64_t task_id, uint32_t group_id, + Lardon3DProjectDbAcquisitionCampaignCapture *capture); Lardon3DProjectDbResult lardon3d_project_db_allocate_task_id(Lardon3DProjectDb *database, uint64_t *task_id); Lardon3DProjectDbResult lardon3d_project_db_load_task(Lardon3DProjectDb *database, uint64_t task_id, diff --git a/meson.build b/meson.build index 66bc491..fbd2a4e 100644 --- a/meson.build +++ b/meson.build @@ -152,6 +152,7 @@ executable( 'src/acquisition_pairing.cpp', 'src/acquisition_ingest.cpp', 'src/acquisition_campaign.cpp', + 'src/acquisition_campaign_task.cpp', 'src/feature_extractor_opencv.cpp', 'src/feature_store.c', 'src/feature_extractor_opencv.cpp', @@ -195,6 +196,7 @@ executable( '-DLARDON3D_TRACK_BUILDER_TASK_AVAILABLE', '-DLARDON3D_SPARSE_SFM_TASK_AVAILABLE', '-DLARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_AVAILABLE', + '-DLARDON3D_ACQUISITION_CAMPAIGN_TASK_AVAILABLE', ], dependencies: [ ncursesw, @@ -377,6 +379,24 @@ acquisition_campaign_test = executable( test('acquisition-campaign', acquisition_campaign_test, timeout: 30) +acquisition_campaign_task_test = executable( + 'test-acquisition-campaign-task', + sources: [ + 'tests/test_acquisition_campaign_task.cpp', + 'src/acquisition_campaign_task.cpp', 'src/acquisition_campaign.cpp', + 'src/acquisition_ingest.cpp', 'src/acquisition_pairing.cpp', + 'src/raw_development.cpp', 'src/app_state.c', 'src/image_catalog_persistent.c', + 'src/image_catalog.c', 'src/image_view.c', 'src/task_kind_registry.c', + 'src/project.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c', + 'src/task.c', 'src/task_checkpoint.c', 'src/task_queue.c', + 'src/resource_governor.c', 'src/resource_snapshot.c', + ], + include_directories: include_directories('include'), + cpp_args: ['-DLARDON3D_ACQUISITION_CAMPAIGN_TASK_TESTING'], + dependencies: [threads, sqlite3, openssl, opencv, libraw, libexif, libpng, libdeflate], +) +test('acquisition-campaign-task', acquisition_campaign_task_test, timeout: 30) + feature_store_test = executable( 'test-feature-store', sources: [ diff --git a/src/acquisition_campaign_task.cpp b/src/acquisition_campaign_task.cpp new file mode 100644 index 0000000..1f993ae --- /dev/null +++ b/src/acquisition_campaign_task.cpp @@ -0,0 +1,569 @@ +extern "C" { +#include +#include +#include +#include +} + +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr unsigned char magic[8] = {'L', '3', 'D', 'A', 'C', 'T', '1', '\0'}; +constexpr size_t max_request_size = + LARDON3D_ACQUISITION_CAMPAIGN_TASK_REQUEST_MAX_BYTES; + +struct Writer { + unsigned char *p; + size_t left; + size_t used; + bool bytes(const void *v, size_t n) { + if (n > left) + return false; + if (p) + std::memcpy(p + used, v, n); + used += n; + left -= n; + return true; + } + bool u16(uint16_t v) { + unsigned char b[2] = {static_cast(v), + static_cast(v >> 8)}; + return bytes(b, 2); + } + bool u32(uint32_t v) { + unsigned char b[4]; + for (int i = 0; i < 4; ++i) + b[i] = static_cast(v >> (8 * i)); + return bytes(b, 4); + } + bool u64(uint64_t v) { + unsigned char b[8]; + for (int i = 0; i < 8; ++i) + b[i] = static_cast(v >> (8 * i)); + return bytes(b, 8); + } + bool text(const char *s, size_t cap) { + if (!s) + return false; + const void *end = std::memchr(s, '\0', cap); + if (!end) + return false; + size_t n = static_cast(end) - s; + return n <= UINT16_MAX && u16(static_cast(n)) && bytes(s, n); + } +}; + +struct Reader { + const unsigned char *p; + size_t left; + bool bytes(void *v, size_t n) { + if (n > left) + return false; + std::memcpy(v, p, n); + p += n; + left -= n; + return true; + } + bool u16(uint16_t &v) { + unsigned char b[2]; + if (!bytes(b, 2)) + return false; + v = uint16_t(b[0]) | uint16_t(uint16_t(b[1]) << 8); + return true; + } + bool u32(uint32_t &v) { + unsigned char b[4]; + if (!bytes(b, 4)) + return false; + v = 0; + for (int i = 3; i >= 0; --i) + v = (v << 8) | b[i]; + return true; + } + bool u64(uint64_t &v) { + unsigned char b[8]; + if (!bytes(b, 8)) + return false; + v = 0; + for (int i = 7; i >= 0; --i) + v = (v << 8) | b[i]; + return true; + } + bool text(char *s, size_t cap) { + uint16_t n; + if (!u16(n) || size_t(n) >= cap || size_t(n) > left) + return false; + std::memcpy(s, p, n); + s[n] = '\0'; + p += n; + left -= n; + return true; + } +}; + +bool encode(const Lardon3DAcquisitionCampaignTaskRequest *r, Writer &w) { + if (!r || !r->sources || r->source_count == 0 || + r->source_count > LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES || + (r->confirmation_count && !r->confirmations) || + r->confirmation_count > r->source_count || + r->ingest_options.representation < + LARDON3D_ACQUISITION_SELECT_JPEG_SOURCE || + r->ingest_options.representation > + LARDON3D_ACQUISITION_SELECT_DEVELOP_RAW || + r->ingest_options.select_representation > 1u || + r->ingest_options.imported_at < 0 || + r->ingest_options.max_source_bytes == 0u) + return false; + std::unique_ptr validated( + new (std::nothrow) Lardon3DAcquisitionCampaignPlan{}); + if (!validated || + lardon3d_acquisition_campaign_plan( + r->sources, r->source_count, r->confirmations, r->confirmation_count, + validated.get()) != LARDON3D_ACQUISITION_CAMPAIGN_OK || + validated->group_count == 0) + return false; + if (!w.bytes(magic, sizeof(magic)) || + !w.u32(LARDON3D_ACQUISITION_CAMPAIGN_REQUEST_VERSION) || + !w.u32(static_cast(r->source_count)) || + !w.u32(static_cast(r->confirmation_count)) || + !w.u32(static_cast(r->ingest_options.representation)) || + !w.u32(r->ingest_options.select_representation ? 1u : 0u) || + !w.u64(static_cast(r->ingest_options.imported_at)) || + !w.u64(r->ingest_options.max_source_bytes)) + return false; + for (size_t i = 0; i < r->source_count; ++i) { + const auto &s = r->sources[i]; + const auto &m = s.metadata; + if (!w.text(s.path, sizeof(s.path)) || + !w.u32(static_cast(s.source_kind)) || + !w.u32(static_cast(s.metadata_result)) || + !w.u32(m.policy_version) || + !w.u32(static_cast(m.source_kind)) || + !w.u32(m.present_fields) || !w.text(m.make, sizeof(m.make)) || + !w.text(m.model, sizeof(m.model)) || + !w.text(m.body_serial, sizeof(m.body_serial)) || + !w.text(m.datetime_original, sizeof(m.datetime_original)) || + !w.text(m.subsec_original, sizeof(m.subsec_original)) || + !w.text(m.offset_original, sizeof(m.offset_original)) || + !w.text(m.image_unique_id, sizeof(m.image_unique_id)) || + !w.u32(m.width) || !w.u32(m.height) || !w.u16(m.orientation)) + return false; + } + for (size_t i = 0; i < r->confirmation_count; ++i) { + const auto &c = r->confirmations[i]; + if (c.source_count == 0 || + c.source_count > LARDON3D_ACQUISITION_INGEST_MAX_SOURCES || + !w.u32(static_cast(c.source_count))) + return false; + for (size_t j = 0; j < c.source_count; ++j) + if (c.source_indices[j] >= r->source_count || + !w.u32(static_cast(c.source_indices[j]))) + return false; + } + return true; +} + +struct Context { + char project_path[LARDON3D_APP_STATE_PATH_CAPACITY]{}; + Lardon3DProjectDb *db{}; + Lardon3DResourceGovernor *governor{}; + uint64_t scanset{}; + std::vector sources; + std::vector confirmations; + std::vector encoded; + Lardon3DAcquisitionCampaignPlan plan{}; + Lardon3DAcquisitionIngestOptions options{}; +}; + +void destroy(void *p) { delete static_cast(p); } +void runtime(Context *c, Lardon3DAppState &s) { + lardon3d_app_state_init(&s); + s.project_loaded = true; + s.project_db = c->db; + s.resource_governor = c->governor; + std::snprintf(s.project_path, sizeof(s.project_path), "%s", c->project_path); +} + +bool checkpoint(Context *c, Lardon3DTask *t, uint32_t cursor) { + Lardon3DAppState s; + runtime(c, s); + Lardon3DProjectDbAcquisitionCampaignTask p{ + lardon3d_task_id(t), + c->scanset, + cursor, + static_cast(c->plan.group_count), + c->encoded.data(), + c->encoded.size()}; + return lardon3d_project_checkpoint_acquisition_campaign_task(&s, t, &p) == + LARDON3D_PROJECT_TASK_CHECKPOINT_OK; +} +bool run_impl(Lardon3DTask *t, void *p) { + auto *c = static_cast(p); + Lardon3DProjectDbAcquisitionCampaignTask persisted{}; + std::vector blob(max_request_size); + if (lardon3d_project_db_load_acquisition_campaign_task( + c->db, lardon3d_task_id(t), blob.data(), blob.size(), &persisted) != + LARDON3D_PROJECT_DB_OK) + return lardon3d_task_fail(t, "Campagne durable introuvable."); + for (uint32_t cursor = persisted.next_group_id; cursor < c->plan.group_count; + ++cursor) { + const uint32_t group_id = cursor + 1u; + if (!lardon3d_task_checkpoint(t)) + return false; + Lardon3DProjectDbAcquisitionCampaignCapture retained{}; + uint64_t resume = 0; + auto lr = lardon3d_project_db_load_acquisition_campaign_capture( + c->db, lardon3d_task_id(t), group_id, &retained); + if (lr == LARDON3D_PROJECT_DB_OK) + resume = retained.capture_id; + else if (lr != LARDON3D_PROJECT_DB_NOT_FOUND) + return lardon3d_task_fail(t, "Lecture de rétention impossible."); + auto options = c->options; + options.grouping = LARDON3D_ACQUISITION_GROUP_CALLER_EXPLICIT; + options.resume_capture_id = resume; + options.producer_task_id = lardon3d_task_id(t); + Lardon3DAcquisitionIngestOutput out{}; + Lardon3DAcquisitionIngestResult ir{}; + Lardon3DAppState s; + runtime(c, s); + auto cr = lardon3d_acquisition_campaign_materialize_group( + &s, c->scanset, c->sources.data(), c->sources.size(), &c->plan, + group_id, &options, &out, &ir); + if (cr != LARDON3D_ACQUISITION_CAMPAIGN_OK || + ir != LARDON3D_ACQUISITION_INGEST_OK || out.group_count != 1) + return lardon3d_task_fail(t, "Échec de matérialisation de campagne."); +#ifdef LARDON3D_ACQUISITION_CAMPAIGN_TASK_TESTING + const char *before_retention = + std::getenv("LARDON3D_TEST_CAMPAIGN_FAIL_BEFORE_RETENTION"); + if (before_retention && std::strcmp(before_retention, "1") == 0) + return lardon3d_task_fail(t, "Échec injecté avant rétention."); +#endif + if (lardon3d_project_db_retain_acquisition_campaign_capture( + c->db, lardon3d_task_id(t), group_id, out.groups[0].capture_id, + group_id) != LARDON3D_PROJECT_DB_OK) + return lardon3d_task_fail(t, "Rétention de Capture impossible."); +#ifdef LARDON3D_ACQUISITION_CAMPAIGN_TASK_TESTING + const char *after_retention = + std::getenv("LARDON3D_TEST_CAMPAIGN_FAIL_AFTER_RETENTION"); + if (after_retention && std::strcmp(after_retention, "1") == 0) + return lardon3d_task_fail(t, "Échec injecté après rétention."); +#endif + unsigned progress = static_cast( + (uint64_t(group_id) * 100u / c->plan.group_count)); + if (!lardon3d_task_set_progress(t, progress, + "Groupe de campagne matérialisé.") || + !checkpoint(c, t, group_id)) + return lardon3d_task_fail(t, "Checkpoint de campagne impossible."); + if (group_id < c->plan.group_count) { + Lardon3DTaskExecutionContract contract{}; + Lardon3DResourceReservation *reservation = nullptr; + if (!lardon3d_task_sequence_break(t, c->governor, &reservation, + &contract)) + return false; + } + } + return true; +} +bool run(Lardon3DTask *t, void *p) noexcept { + try { + return run_impl(t, p); + } catch (const std::bad_alloc &) { + return lardon3d_task_fail(t, "Mémoire insuffisante pour la campagne."); + } catch (...) { + return lardon3d_task_fail(t, "Erreur interne de campagne."); + } +} +void finished(const Lardon3DTask *t, void *p) { + try { + auto *c = static_cast(p); + Lardon3DAppState s; + runtime(c, s); + (void)lardon3d_project_checkpoint_task(&s, t); + } catch (...) { + } +} + +Context *make_context(const char *path, Lardon3DProjectDb *db, + Lardon3DResourceGovernor *g, uint64_t scanset, + const Lardon3DAcquisitionCampaignTaskRequest &r, + const unsigned char *encoded, size_t n) { + if (!path || !path[0]) + return nullptr; + std::unique_ptr c(new (std::nothrow) Context); + if (!c) + return nullptr; + const int path_size = + std::snprintf(c->project_path, sizeof(c->project_path), "%s", path); + if (path_size < 0 || static_cast(path_size) >= sizeof(c->project_path)) + return nullptr; + c->db = db; + c->governor = g; + c->scanset = scanset; + c->sources.assign(r.sources, r.sources + r.source_count); + c->confirmations.assign(r.confirmations, + r.confirmations + r.confirmation_count); + c->options = r.ingest_options; + c->encoded.assign(encoded, encoded + n); + if (lardon3d_acquisition_campaign_plan(c->sources.data(), c->sources.size(), + c->confirmations.data(), + c->confirmations.size(), &c->plan) != + LARDON3D_ACQUISITION_CAMPAIGN_OK || + c->plan.group_count == 0) + return nullptr; + return c.release(); +} +} // namespace + +bool request_encode_impl( + const Lardon3DAcquisitionCampaignTaskRequest *r, unsigned char *out, + size_t cap, size_t *size) { + if (size) + *size = 0; + if (!size) + return false; + Writer measure{nullptr, max_request_size, 0}; + if (!encode(r, measure) || measure.used > max_request_size) { + return false; + } + *size = measure.used; + if (!out) + return cap == 0; + if (cap < measure.used) + return false; + Writer writer{out, cap, 0}; + return encode(r, writer) && writer.used == measure.used; +} + +bool request_decode_impl( + const unsigned char *input, size_t size, + Lardon3DAcquisitionCampaignSource *sources, size_t sc, + Lardon3DAcquisitionCampaignConfirmation *confirmations, size_t cc, + Lardon3DAcquisitionCampaignTaskRequest *r) { + if (!input || !r || size > max_request_size) + return false; + std::memset(r, 0, sizeof(*r)); + Reader q{input, size}; + unsigned char m[8]; + uint32_t version, n, c, rep, select; + uint64_t imported, maxbytes; + if (!q.bytes(m, 8) || std::memcmp(m, magic, 8) || !q.u32(version) || + version != 1 || !q.u32(n) || n == 0 || n > sc || + n > LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES || !q.u32(c) || c > cc || + c > n || !sources || (c > 0 && !confirmations) || !q.u32(rep) || + rep < 1 || rep > 2 || !q.u32(select) || select > 1 || + !q.u64(imported) || imported > INT64_MAX || !q.u64(maxbytes) || + maxbytes == 0u) + return false; + std::memset(sources, 0, n * sizeof(*sources)); + std::memset(confirmations, 0, c * sizeof(*confirmations)); + for (uint32_t i = 0; i < n; ++i) { + auto &s = sources[i]; + auto &md = s.metadata; + uint32_t sk, mr, msk; + if (!q.text(s.path, sizeof(s.path)) || !q.u32(sk) || sk > 2 || !q.u32(mr) || + mr > LARDON3D_ACQUISITION_INTERNAL_ERROR || !q.u32(md.policy_version) || + !q.u32(msk) || msk > 2 || !q.u32(md.present_fields) || + !q.text(md.make, sizeof(md.make)) || + !q.text(md.model, sizeof(md.model)) || + !q.text(md.body_serial, sizeof(md.body_serial)) || + !q.text(md.datetime_original, sizeof(md.datetime_original)) || + !q.text(md.subsec_original, sizeof(md.subsec_original)) || + !q.text(md.offset_original, sizeof(md.offset_original)) || + !q.text(md.image_unique_id, sizeof(md.image_unique_id)) || + !q.u32(md.width) || !q.u32(md.height) || !q.u16(md.orientation)) + return false; + s.source_kind = static_cast(sk); + s.metadata_result = static_cast(mr); + md.source_kind = static_cast(msk); + } + for (uint32_t i = 0; i < c; ++i) { + uint32_t count; + if (!q.u32(count) || count == 0 || + count > LARDON3D_ACQUISITION_INGEST_MAX_SOURCES) + return false; + confirmations[i].source_count = count; + for (uint32_t j = 0; j < count; ++j) { + uint32_t index; + if (!q.u32(index) || index >= n) + return false; + confirmations[i].source_indices[j] = index; + } + } + if (q.left) + return false; + r->sources = sources; + r->source_count = n; + r->confirmations = confirmations; + r->confirmation_count = c; + r->ingest_options.representation = + static_cast(rep); + r->ingest_options.select_representation = static_cast(select); + r->ingest_options.imported_at = static_cast(imported); + r->ingest_options.max_source_bytes = maxbytes; + std::unique_ptr validated( + new (std::nothrow) Lardon3DAcquisitionCampaignPlan{}); + return validated && + lardon3d_acquisition_campaign_plan(sources, n, confirmations, c, + validated.get()) == + LARDON3D_ACQUISITION_CAMPAIGN_OK && + validated->group_count > 0; +} + +bool task_reconstruct_impl( + const Lardon3DTaskDurableSnapshot *s, void *userdata, + Lardon3DTaskKindBinding *b) { + auto *rt = static_cast(userdata); + if (!s || !rt || !b) + return false; + std::vector blob(max_request_size); + Lardon3DProjectDbAcquisitionCampaignTask p{}; + if (lardon3d_project_db_load_acquisition_campaign_task( + rt->project_db, s->id, blob.data(), blob.size(), &p) != + LARDON3D_PROJECT_DB_OK) + return false; + std::vector sources( + LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES); + std::vector confirmations( + LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES); + Lardon3DAcquisitionCampaignTaskRequest r{}; + if (!lardon3d_acquisition_campaign_request_decode( + blob.data(), p.request_size, sources.data(), sources.size(), + confirmations.data(), confirmations.size(), &r)) + return false; + auto *c = + make_context(rt->project_path, rt->project_db, rt->resource_governor, + p.scanset_id, r, blob.data(), p.request_size); + if (!c) + return false; + *b = {run, c, destroy, finished, c}; + return true; +} + +Lardon3DTask *create_task_impl( + Lardon3DAppState *s, uint64_t scanset, + const Lardon3DAcquisitionCampaignTaskRequest *r, uint64_t *id) { + if (id) + *id = 0; + if (!s || !s->project_loaded || !s->project_db || !s->resource_governor || + !r || !id) + return nullptr; + Lardon3DProjectDbScanSet ss{}; + if (lardon3d_project_db_load_scanset(s->project_db, scanset, &ss) != + LARDON3D_PROJECT_DB_OK) + return nullptr; + size_t n = 0; + if (!lardon3d_acquisition_campaign_request_encode(r, nullptr, 0, &n)) + return nullptr; + std::vector blob(n); + if (!lardon3d_acquisition_campaign_request_encode(r, blob.data(), blob.size(), + &n)) + return nullptr; + auto *c = make_context(s->project_path, s->project_db, s->resource_governor, + scanset, *r, blob.data(), n); + if (!c) + return nullptr; + uint64_t task_id; + if (lardon3d_project_db_allocate_task_id(s->project_db, &task_id) != + LARDON3D_PROJECT_DB_OK) { + delete c; + return nullptr; + } + Lardon3DResourceEstimate e{ + 256 * 1024, 0, 64 * 1024, 0, 1, + 1, 1, 0, 1, LARDON3D_RESOURCE_TASK_IMPORT}; + auto *t = lardon3d_task_create_typed("Campagne d'acquisition", &e, + LARDON3D_ACQUISITION_CAMPAIGN_TASK_KIND, + 1, run, c, destroy); + if (!t || !lardon3d_task_assign_id(t, task_id) || + !lardon3d_task_set_finished_callback(t, finished, c) || + !checkpoint(c, t, 0)) { + lardon3d_task_destroy(t); + return nullptr; + } + *id = task_id; + return t; +} + +extern "C" bool lardon3d_acquisition_campaign_request_encode( + const Lardon3DAcquisitionCampaignTaskRequest *r, unsigned char *out, + size_t cap, size_t *size) { + try { + return request_encode_impl(r, out, cap, size); + } catch (const std::bad_alloc &) { + if (size) + *size = 0; + return false; + } catch (...) { + if (size) + *size = 0; + return false; + } +} + +extern "C" bool lardon3d_acquisition_campaign_request_decode( + const unsigned char *input, size_t size, + Lardon3DAcquisitionCampaignSource *sources, size_t sc, + Lardon3DAcquisitionCampaignConfirmation *confirmations, size_t cc, + Lardon3DAcquisitionCampaignTaskRequest *r) { + try { + return request_decode_impl(input, size, sources, sc, confirmations, cc, r); + } catch (const std::bad_alloc &) { + return false; + } catch (...) { + return false; + } +} + +extern "C" bool lardon3d_acquisition_campaign_task_reconstruct( + const Lardon3DTaskDurableSnapshot *s, void *userdata, + Lardon3DTaskKindBinding *b) { + try { + return task_reconstruct_impl(s, userdata, b); + } catch (const std::bad_alloc &) { + return false; + } catch (...) { + return false; + } +} + +extern "C" Lardon3DTask *lardon3d_project_create_acquisition_campaign_task( + Lardon3DAppState *s, uint64_t scanset, + const Lardon3DAcquisitionCampaignTaskRequest *r, uint64_t *id) { + if (id) + *id = 0; + try { + return create_task_impl(s, scanset, r, id); + } catch (const std::bad_alloc &) { + return nullptr; + } catch (...) { + return nullptr; + } +} + +extern "C" bool lardon3d_project_enqueue_acquisition_campaign( + Lardon3DAppState *s, uint64_t scanset, + const Lardon3DAcquisitionCampaignTaskRequest *r, uint64_t *id) { + try { + if (!s || !s->task_queue) + return false; + auto *t = + lardon3d_project_create_acquisition_campaign_task(s, scanset, r, id); + if (!t) + return false; + if (!lardon3d_task_queue_add(s->task_queue, t, nullptr)) { + lardon3d_task_destroy(t); + return false; + } + return true; + } catch (const std::bad_alloc &) { + return false; + } catch (...) { + return false; + } +} diff --git a/src/project.c b/src/project.c index a1d164a..777deae 100644 --- a/src/project.c +++ b/src/project.c @@ -674,7 +674,8 @@ checkpoint_task_internal(Lardon3DAppState *state, const Lardon3DTask *task, const Lardon3DProjectDbVisualIndexUpdateTask *visual_parameters, const Lardon3DProjectDbCandidatePairGenerateTask *candidate_parameters, const Lardon3DProjectDbMatcherTask *matcher_parameters, - const Lardon3DProjectDbGeometricVerifierTask *geometric_parameters) { + const Lardon3DProjectDbGeometricVerifierTask *geometric_parameters, + const Lardon3DProjectDbAcquisitionCampaignTask *campaign_parameters) { if (!state || !state->project_loaded || !state->project_db) { return LARDON3D_PROJECT_TASK_CHECKPOINT_NO_PROJECT; } @@ -741,6 +742,10 @@ checkpoint_task_internal(Lardon3DAppState *state, const Lardon3DTask *task, ? lardon3d_project_db_record_geometric_verifier_task( state->project_db, &snapshot, task_kind, task_kind_version, &checkpoint, geometric_parameters, now.tv_sec) + : campaign_parameters + ? lardon3d_project_db_record_acquisition_campaign_task( + state->project_db, &snapshot, task_kind, task_kind_version, &checkpoint, + campaign_parameters, now.tv_sec) : lardon3d_project_db_record_task(state->project_db, &snapshot, task_kind, task_kind_version, &checkpoint, now.tv_sec); if (recorded == LARDON3D_PROJECT_DB_BUSY) { @@ -756,7 +761,7 @@ checkpoint_task_internal(Lardon3DAppState *state, const Lardon3DTask *task, Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_task(Lardon3DAppState *state, const Lardon3DTask *task) { - return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL); + return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } Lardon3DProjectTaskCheckpointResult @@ -766,7 +771,15 @@ lardon3d_project_checkpoint_image_import_task(Lardon3DAppState *state, const Lar return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK; } return checkpoint_task_internal( - state, task, source_path, scanset_id, NULL, NULL, NULL, NULL, NULL, NULL); + state, task, source_path, scanset_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +} + +Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_acquisition_campaign_task( + Lardon3DAppState *state, const Lardon3DTask *task, + const Lardon3DProjectDbAcquisitionCampaignTask *parameters) { + if (!parameters) return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK; + return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, + parameters); } Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_feature_extract_task( @@ -775,14 +788,16 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_feature_extract_ if (!parameters) { return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK; } - return checkpoint_task_internal(state, task, NULL, 0, parameters, NULL, NULL, NULL, NULL, NULL); + return checkpoint_task_internal(state, task, NULL, 0, parameters, NULL, NULL, NULL, NULL, NULL, + NULL); } Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_sift_extract_task( Lardon3DAppState *state, const Lardon3DTask *task, const Lardon3DProjectDbSiftExtractTask *parameters) { if (!parameters) return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK; - return checkpoint_task_internal(state, task, NULL, 0, NULL, parameters, NULL, NULL, NULL, NULL); + return checkpoint_task_internal(state, task, NULL, 0, NULL, parameters, NULL, NULL, NULL, NULL, + NULL); } Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_visual_index_update_task( @@ -791,7 +806,8 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_visual_index_upd if (!parameters) { return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK; } - return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, parameters, NULL, NULL, NULL); + return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, parameters, NULL, NULL, NULL, + NULL); } Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_candidate_pair_generate_task( @@ -800,7 +816,8 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_candidate_pair_g if (!parameters) { return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK; } - return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, parameters, NULL, NULL); + return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, parameters, NULL, NULL, + NULL); } Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_matcher_task( @@ -809,7 +826,8 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_matcher_task( if (!parameters) { return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK; } - return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, parameters, NULL); + return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, parameters, NULL, + NULL); } Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_geometric_verifier_task( @@ -819,7 +837,7 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_geometric_verifi return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK; } return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, NULL, - parameters); + parameters, NULL); } static bool coherent_recovery(const Lardon3DProjectDbTask *database_task, diff --git a/src/project_db.c b/src/project_db.c index bf83094..11e1aa6 100644 --- a/src/project_db.c +++ b/src/project_db.c @@ -607,6 +607,19 @@ static const char schema_capture_provenance_v19[] = "created_at INTEGER NOT NULL CHECK(created_at>=0)," "CHECK(parent_asset_id!=child_asset_id));"; +static const char schema_acquisition_campaign_v20[] = + "CREATE TABLE IF NOT EXISTS acquisition_campaign_tasks(" + "task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE," + "scanset_id INTEGER NOT NULL REFERENCES scansets(scanset_id)," + "next_group_id INTEGER NOT NULL CHECK(next_group_id>=0 AND next_group_id<=4294967295)," + "group_count INTEGER NOT NULL CHECK(group_count>0 AND group_count<=4096)," + "request BLOB NOT NULL CHECK(length(request)>0));" + "CREATE TABLE IF NOT EXISTS acquisition_campaign_captures(" + "task_id INTEGER NOT NULL REFERENCES acquisition_campaign_tasks(task_id) ON DELETE CASCADE," + "group_id INTEGER NOT NULL CHECK(group_id>=0 AND group_id<=4294967295)," + "capture_id INTEGER NOT NULL REFERENCES captures(capture_id)," + "PRIMARY KEY(task_id,group_id),UNIQUE(task_id,capture_id));"; + static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) { if (destination) { (void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : ""); @@ -1093,6 +1106,22 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int "finish schema v19 migration"); } } + if (result == LARDON3D_PROJECT_DB_OK && from_version < 20) { + result = execute(database, schema_acquisition_campaign_v20, + "migrate acquisition campaign task v19 to v20"); +#ifdef LARDON3D_PROJECT_DB_TESTING + if (result == LARDON3D_PROJECT_DB_OK && + getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V20")) { + result = execute(database, "INSERT INTO missing_v20_test_table VALUES(1)", + "forced migration v20 failure"); + } +#endif + if (result == LARDON3D_PROJECT_DB_OK) { + result = execute(database, + "UPDATE metadata SET value=20 WHERE key='schema_version' AND value=19", + "finish schema v20 migration"); + } + } if (result == LARDON3D_PROJECT_DB_OK) { result = execute(database, "COMMIT", "commit migration"); } @@ -1228,7 +1257,9 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje "capture_images", "capture_assets", "capture_selections", - "asset_derivations"}; + "asset_derivations", + "acquisition_campaign_tasks", + "acquisition_campaign_captures"}; for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) && result == LARDON3D_PROJECT_DB_OK; ++index) { @@ -1452,6 +1483,7 @@ record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnaps const Lardon3DProjectDbMatcherTask *matcher, const Lardon3DProjectDbGeometricVerifierTask *geometric_verifier, const Lardon3DProjectDbSparseSfmTask *sparse_sfm, + const Lardon3DProjectDbAcquisitionCampaignTask *campaign, int64_t updated_at) { bool typed = task_kind != NULL; if (!database || !valid_durable_task(snapshot, updated_at) || @@ -1518,6 +1550,11 @@ record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnaps !valid_task_id(sparse_sfm->track_set_id) || !valid_task_id(sparse_sfm->calibration_scope_id) || sparse_sfm->sfm_kind != 1 || sparse_sfm->sfm_version != 1 || !valid_sparse_sfm_parameters(&sparse_sfm->parameters))) || + (campaign && (!valid_task_id(campaign->task_id) || campaign->task_id != snapshot->id || + !valid_task_id(campaign->scanset_id) || campaign->group_count == 0 || + campaign->group_count > 4096 || + campaign->next_group_id > campaign->group_count || !campaign->request || + campaign->request_size == 0 || campaign->request_size > INT_MAX)) || (checkpoint && !valid_checkpoint(checkpoint))) { return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; } @@ -1582,6 +1619,26 @@ record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnaps } #endif } + if (result == LARDON3D_PROJECT_DB_OK && campaign) { + result = prepare(database, + "INSERT INTO acquisition_campaign_tasks(task_id,scanset_id,next_group_id,group_count," + "request) VALUES(?1,?2,?3,?4,?5) ON CONFLICT(task_id) DO UPDATE SET " + "next_group_id=excluded.next_group_id WHERE scanset_id=excluded.scanset_id AND " + "group_count=excluded.group_count AND request=excluded.request", &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)campaign->task_id); + sqlite3_bind_int64(statement, 2, (sqlite3_int64)campaign->scanset_id); + sqlite3_bind_int64(statement, 3, campaign->next_group_id); + sqlite3_bind_int64(statement, 4, campaign->group_count); + sqlite3_bind_blob(statement, 5, campaign->request, (int)campaign->request_size, + SQLITE_TRANSIENT); + result = step_done(database, statement, "upsert campaign task"); + if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) { + copy_error(database->error, "Payload de campagne immuable."); + result = LARDON3D_PROJECT_DB_CONSTRAINT; + } + } + } if (result == LARDON3D_PROJECT_DB_OK) { result = prepare(database, @@ -1916,7 +1973,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_task( Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot, const char *task_kind, uint32_t task_kind_version, const Lardon3DProjectDbCheckpoint *checkpoint, int64_t updated_at) { return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, updated_at); + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, updated_at); } Lardon3DProjectDbResult lardon3d_project_db_record_image_import_task( @@ -1928,7 +1985,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_image_import_task( } return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, source_path, scanset_id, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, updated_at); + NULL, NULL, updated_at); } Lardon3DProjectDbResult lardon3d_project_db_record_feature_extract_task( @@ -1939,7 +1996,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_feature_extract_task( return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; } return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0, - parameters, NULL, NULL, NULL, NULL, NULL, NULL, updated_at); + parameters, NULL, NULL, NULL, NULL, NULL, NULL, NULL, updated_at); } Lardon3DProjectDbResult lardon3d_project_db_record_sift_extract_task( @@ -1949,7 +2006,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_sift_extract_task( const Lardon3DProjectDbSiftExtractTask *parameters, int64_t updated_at) { if (!parameters) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0, - NULL, parameters, NULL, NULL, NULL, NULL, NULL, updated_at); + NULL, parameters, NULL, NULL, NULL, NULL, NULL, NULL, updated_at); } static bool read_task(sqlite3_stmt *statement, Lardon3DProjectDbTask *task) { @@ -2179,6 +2236,158 @@ lardon3d_project_db_load_image_import(Lardon3DProjectDb *database, uint64_t task return result; } +Lardon3DProjectDbResult lardon3d_project_db_record_acquisition_campaign_task( + Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot, + const char *task_kind, uint32_t task_kind_version, + const Lardon3DProjectDbCheckpoint *checkpoint, + const Lardon3DProjectDbAcquisitionCampaignTask *parameters, int64_t updated_at) { + if (!database || !snapshot || !parameters || parameters->task_id != snapshot->id || + !valid_task_id(parameters->scanset_id) || parameters->group_count == 0 || + parameters->group_count > 4096 || parameters->next_group_id > parameters->group_count || + !parameters->request || parameters->request_size == 0 || parameters->request_size > INT_MAX) { + return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; + } + return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, + NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, parameters, + updated_at); +} + +Lardon3DProjectDbResult lardon3d_project_db_load_acquisition_campaign_task( + Lardon3DProjectDb *database, uint64_t task_id, unsigned char *request, + size_t request_capacity, Lardon3DProjectDbAcquisitionCampaignTask *parameters) { + if (!database || !valid_task_id(task_id) || !request || request_capacity == 0 || !parameters) + return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; + memset(parameters, 0, sizeof(*parameters)); + (void)pthread_mutex_lock(&database->mutex); + sqlite3_stmt *statement = NULL; + Lardon3DProjectDbResult result = prepare(database, + "SELECT scanset_id,next_group_id,group_count,request FROM acquisition_campaign_tasks " + "WHERE task_id=?1", &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id); + int code = sqlite3_step(statement); + if (code == SQLITE_DONE) result = LARDON3D_PROJECT_DB_NOT_FOUND; + else if (code != SQLITE_ROW) result = sqlite_result(database, code, "load campaign task"); + else { + int bytes = sqlite3_column_bytes(statement, 3); + const void *blob = sqlite3_column_blob(statement, 3); + if (bytes <= 0 || (size_t)bytes > request_capacity || !blob) { + result = LARDON3D_PROJECT_DB_CONSTRAINT; + } else { + parameters->task_id = task_id; + parameters->scanset_id = (uint64_t)sqlite3_column_int64(statement, 0); + parameters->next_group_id = (uint32_t)sqlite3_column_int64(statement, 1); + parameters->group_count = (uint32_t)sqlite3_column_int64(statement, 2); + memcpy(request, blob, (size_t)bytes); + parameters->request = request; + parameters->request_size = (size_t)bytes; + } + } + } + sqlite3_finalize(statement); + (void)pthread_mutex_unlock(&database->mutex); + return result; +} + +Lardon3DProjectDbResult lardon3d_project_db_retain_acquisition_campaign_capture( + Lardon3DProjectDb *database, uint64_t task_id, uint32_t group_id, + uint64_t capture_id, uint32_t next_group_id) { + if (!database || !valid_task_id(task_id) || !valid_task_id(capture_id) || + group_id == 0 || next_group_id != group_id) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; + (void)pthread_mutex_lock(&database->mutex); + Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin capture retention"); + sqlite3_stmt *statement = NULL; + uint32_t cursor = 0; + uint32_t group_count = 0; + uint64_t retained_capture_id = 0; + if (result == LARDON3D_PROJECT_DB_OK) { + result = prepare(database, + "SELECT t.next_group_id,t.group_count,COALESCE(m.capture_id,0)," + "EXISTS(SELECT 1 FROM captures c WHERE c.capture_id=?3 AND " + "c.scanset_id=t.scanset_id) FROM acquisition_campaign_tasks t LEFT JOIN " + "acquisition_campaign_captures m ON m.task_id=t.task_id AND m.group_id=?2 " + "WHERE t.task_id=?1", &statement); + } + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id); + sqlite3_bind_int64(statement, 2, group_id); + sqlite3_bind_int64(statement, 3, (sqlite3_int64)capture_id); + int code = sqlite3_step(statement); + if (code == SQLITE_DONE) result = LARDON3D_PROJECT_DB_NOT_FOUND; + else if (code != SQLITE_ROW) result = sqlite_result(database, code, "validate capture retention"); + else { + cursor = (uint32_t)sqlite3_column_int64(statement, 0); + group_count = (uint32_t)sqlite3_column_int64(statement, 1); + retained_capture_id = (uint64_t)sqlite3_column_int64(statement, 2); + bool matching_scanset = sqlite3_column_int(statement, 3) == 1; + bool new_mapping = cursor + 1u == group_id && retained_capture_id == 0; + bool exact_retry = cursor == next_group_id && retained_capture_id == capture_id; + if (group_id > group_count || !matching_scanset || (!new_mapping && !exact_retry)) + result = LARDON3D_PROJECT_DB_CONSTRAINT; + } + } + sqlite3_finalize(statement); + statement = NULL; + if (result == LARDON3D_PROJECT_DB_OK && retained_capture_id == 0) + result = prepare(database, + "INSERT INTO acquisition_campaign_captures(task_id,group_id,capture_id) VALUES(?1,?2,?3)", + &statement); + if (result == LARDON3D_PROJECT_DB_OK && retained_capture_id == 0) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id); + sqlite3_bind_int64(statement, 2, group_id); + sqlite3_bind_int64(statement, 3, (sqlite3_int64)capture_id); + result = step_done(database, statement, "retain campaign capture"); + if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) + result = LARDON3D_PROJECT_DB_CONSTRAINT; + } + if (result == LARDON3D_PROJECT_DB_OK && cursor + 1u == group_id) { + result = prepare(database, + "UPDATE acquisition_campaign_tasks SET next_group_id=?2 WHERE task_id=?1 AND " + "next_group_id=?3 AND group_count>=?2", &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id); + sqlite3_bind_int64(statement, 2, next_group_id); + sqlite3_bind_int64(statement, 3, (sqlite3_int64)group_id - 1); + result = step_done(database, statement, "advance campaign cursor"); + if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) + result = LARDON3D_PROJECT_DB_CONSTRAINT; + } + } + if (result == LARDON3D_PROJECT_DB_OK) + result = execute(database, "COMMIT", "commit capture retention"); + if (result != LARDON3D_PROJECT_DB_OK) + (void)execute(database, "ROLLBACK", "rollback capture retention"); + (void)pthread_mutex_unlock(&database->mutex); + return result; +} + +Lardon3DProjectDbResult lardon3d_project_db_load_acquisition_campaign_capture( + Lardon3DProjectDb *database, uint64_t task_id, uint32_t group_id, + Lardon3DProjectDbAcquisitionCampaignCapture *capture) { + if (!database || !valid_task_id(task_id) || !capture) + return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; + memset(capture, 0, sizeof(*capture)); + (void)pthread_mutex_lock(&database->mutex); + sqlite3_stmt *statement = NULL; + Lardon3DProjectDbResult result = prepare(database, + "SELECT capture_id FROM acquisition_campaign_captures WHERE task_id=?1 AND group_id=?2", + &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id); + sqlite3_bind_int64(statement, 2, group_id); + int code = sqlite3_step(statement); + if (code == SQLITE_DONE) result = LARDON3D_PROJECT_DB_NOT_FOUND; + else if (code != SQLITE_ROW) result = sqlite_result(database, code, "load retained capture"); + else { + capture->group_id = group_id; + capture->capture_id = (uint64_t)sqlite3_column_int64(statement, 0); + } + } + sqlite3_finalize(statement); + (void)pthread_mutex_unlock(&database->mutex); + return result; +} + static bool valid_catalog_id(uint64_t value) { return value > 0 && value <= INT64_MAX; } static bool valid_original_name(const char *name) { @@ -4764,7 +4973,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_visual_index_update_task( return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; } return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL, - NULL, parameters, NULL, NULL, NULL, NULL, updated_at); + NULL, parameters, NULL, NULL, NULL, NULL, NULL, updated_at); } Lardon3DProjectDbResult lardon3d_project_db_load_visual_index_update_task( @@ -4811,7 +5020,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_candidate_pair_generate_task( return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; } return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL, - NULL, NULL, parameters, NULL, NULL, NULL, updated_at); + NULL, NULL, parameters, NULL, NULL, NULL, NULL, updated_at); } Lardon3DProjectDbResult lardon3d_project_db_load_candidate_pair_generate_task( @@ -4867,7 +5076,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_matcher_task( return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; } return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL, - NULL, NULL, NULL, parameters, NULL, NULL, updated_at); + NULL, NULL, NULL, parameters, NULL, NULL, NULL, updated_at); } Lardon3DProjectDbResult lardon3d_project_db_load_matcher_task( @@ -4925,7 +5134,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_geometric_verifier_task( return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; } return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL, - NULL, NULL, NULL, NULL, parameters, NULL, updated_at); + NULL, NULL, NULL, NULL, parameters, NULL, NULL, updated_at); } Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verifier_task( @@ -4998,7 +5207,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_track_builder_task( } Lardon3DProjectDbResult result = record_task_internal( db, snapshot, kind, version, checkpoint, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - updated_at); + NULL, updated_at); if (result != LARDON3D_PROJECT_DB_OK) return result; (void)pthread_mutex_lock(&db->mutex); result = execute(db, "BEGIN IMMEDIATE", "begin track builder task"); @@ -5109,7 +5318,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_sparse_sfm_task( const Lardon3DProjectDbSparseSfmTask *parameters, int64_t updated_at) { if (!parameters) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; return record_task_internal(database, snapshot, kind, version, checkpoint, NULL, 0, NULL, NULL, - NULL, NULL, NULL, NULL, parameters, updated_at); + NULL, NULL, NULL, NULL, parameters, NULL, updated_at); } static bool read_u64_blob(sqlite3_stmt *statement, int column, uint64_t *value) { diff --git a/src/task_kinds.c b/src/task_kinds.c index 6365b46..65bb37c 100644 --- a/src/task_kinds.c +++ b/src/task_kinds.c @@ -1,4 +1,7 @@ #include +#ifdef LARDON3D_ACQUISITION_CAMPAIGN_TASK_AVAILABLE +#include +#endif #include #include #include @@ -18,6 +21,13 @@ const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) { static const Lardon3DTaskKindDescriptor descriptors[] = { +#ifdef LARDON3D_ACQUISITION_CAMPAIGN_TASK_AVAILABLE + { + .kind = LARDON3D_ACQUISITION_CAMPAIGN_TASK_KIND, + .kind_version = LARDON3D_ACQUISITION_CAMPAIGN_TASK_KIND_VERSION, + .reconstruct = lardon3d_acquisition_campaign_task_reconstruct, + }, +#endif { .kind = LARDON3D_IMAGE_IMPORT_TASK_KIND, .kind_version = LARDON3D_IMAGE_IMPORT_TASK_KIND_VERSION, diff --git a/tests/test_acquisition_campaign_task.cpp b/tests/test_acquisition_campaign_task.cpp new file mode 100644 index 0000000..6940c7b --- /dev/null +++ b/tests/test_acquisition_campaign_task.cpp @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +#define CHECK(x) \ + do { \ + if (!(x)) { \ + std::fprintf(stderr, "failure line %d: %s\n", __LINE__, #x); \ + return 1; \ + } \ + } while (0) + +extern "C" const Lardon3DTaskKindRegistry * +lardon3d_task_kind_registry_production(void) { + return nullptr; +} + +int main() { + Lardon3DAcquisitionCampaignSource sources[2]{}; + std::snprintf(sources[0].path, sizeof(sources[0].path), "/input/a.arw"); + std::snprintf(sources[1].path, sizeof(sources[1].path), "/input/a.jpg"); + sources[0].source_kind = sources[0].metadata.source_kind = + LARDON3D_ACQUISITION_SOURCE_RAW; + sources[1].source_kind = sources[1].metadata.source_kind = + LARDON3D_ACQUISITION_SOURCE_JPEG; + for (auto &source : sources) { + source.metadata_result = LARDON3D_ACQUISITION_OK; + source.metadata.policy_version = + LARDON3D_ACQUISITION_PAIRING_POLICY_VERSION; + } + Lardon3DAcquisitionCampaignConfirmation confirmation{}; + confirmation.source_count = 2; + confirmation.source_indices[0] = 0; + confirmation.source_indices[1] = 1; + Lardon3DAcquisitionCampaignTaskRequest input{}; + input.sources = sources; + input.source_count = 2; + input.confirmations = &confirmation; + input.confirmation_count = 1; + input.ingest_options.representation = LARDON3D_ACQUISITION_SELECT_DEVELOP_RAW; + input.ingest_options.select_representation = 1; + input.ingest_options.imported_at = 123; + input.ingest_options.max_source_bytes = 456; + + size_t size = 0; + CHECK( + lardon3d_acquisition_campaign_request_encode(&input, nullptr, 0, &size)); + CHECK(size > 32); + std::vector encoded(size); + CHECK(lardon3d_acquisition_campaign_request_encode(&input, encoded.data(), + encoded.size(), &size)); + Lardon3DAcquisitionCampaignSource decoded_sources[2]{}; + Lardon3DAcquisitionCampaignConfirmation decoded_confirmations[2]{}; + Lardon3DAcquisitionCampaignTaskRequest decoded{}; + CHECK(lardon3d_acquisition_campaign_request_decode( + encoded.data(), encoded.size(), decoded_sources, 2, decoded_confirmations, + 2, &decoded)); + CHECK(decoded.source_count == 2 && decoded.confirmation_count == 1); + CHECK(decoded_confirmations[0].source_count == 2); + CHECK(decoded.ingest_options.representation == + LARDON3D_ACQUISITION_SELECT_DEVELOP_RAW); + CHECK(decoded.ingest_options.select_representation == 1); + CHECK(decoded.ingest_options.imported_at == 123 && + decoded.ingest_options.max_source_bytes == 456); + CHECK(std::strcmp(decoded_sources[0].path, "/input/a.arw") == 0); + CHECK(!lardon3d_acquisition_campaign_request_decode( + encoded.data(), encoded.size(), nullptr, 2, decoded_confirmations, 2, + &decoded)); + CHECK(!lardon3d_acquisition_campaign_request_decode( + encoded.data(), encoded.size(), decoded_sources, 2, nullptr, 2, + &decoded)); + + constexpr size_t imported_at_offset = 8u + 5u * sizeof(uint32_t); + for (size_t i = 0; i < sizeof(uint64_t); ++i) + encoded[imported_at_offset + i] = 0xffu; + CHECK(!lardon3d_acquisition_campaign_request_decode( + encoded.data(), encoded.size(), decoded_sources, 2, decoded_confirmations, + 2, &decoded)); + for (size_t i = 0; i < sizeof(uint64_t); ++i) + encoded[imported_at_offset + i] = + static_cast(uint64_t(123) >> (8u * i)); + + input.ingest_options.imported_at = -1; + CHECK(!lardon3d_acquisition_campaign_request_encode(&input, nullptr, 0, + &size)); + input.ingest_options.imported_at = 123; + input.ingest_options.max_source_bytes = 0; + CHECK(!lardon3d_acquisition_campaign_request_encode(&input, nullptr, 0, + &size)); + input.ingest_options.max_source_bytes = 456; + + encoded[8] = 2; + CHECK(!lardon3d_acquisition_campaign_request_decode( + encoded.data(), encoded.size(), decoded_sources, 2, decoded_confirmations, + 2, &decoded)); + encoded[8] = 1; + CHECK(!lardon3d_acquisition_campaign_request_decode( + encoded.data(), encoded.size() - 1, decoded_sources, 2, + decoded_confirmations, 2, &decoded)); + CHECK(!lardon3d_acquisition_campaign_request_decode( + encoded.data(), encoded.size(), decoded_sources, 1, decoded_confirmations, + 2, &decoded)); + + char database_path[] = "/tmp/lardon3d-campaign-task-XXXXXX"; + int descriptor = mkstemp(database_path); + CHECK(descriptor >= 0 && close(descriptor) == 0 && + unlink(database_path) == 0); + char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{}; + Lardon3DProjectDb *database = nullptr; + CHECK(lardon3d_project_db_open(database_path, &database, error) == + LARDON3D_PROJECT_DB_OK); + Lardon3DProjectDbScanSet target{}, other{}; + CHECK(lardon3d_project_db_create_scanset(database, "target", &target) == + LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_project_db_create_scanset(database, "other", &other) == + LARDON3D_PROJECT_DB_OK); + Lardon3DTaskDurableSnapshot snapshot{}; + snapshot.id = 1; + std::snprintf(snapshot.name, sizeof(snapshot.name), "campaign"); + snapshot.saved_state = snapshot.recovery_state = TASK_PENDING; + Lardon3DProjectDbAcquisitionCampaignTask durable{ + 1, target.scanset_id, 0, 2, encoded.data(), encoded.size()}; + CHECK(lardon3d_project_db_record_acquisition_campaign_task( + database, &snapshot, LARDON3D_ACQUISITION_CAMPAIGN_TASK_KIND, + LARDON3D_ACQUISITION_CAMPAIGN_TASK_KIND_VERSION, nullptr, &durable, + 1) == LARDON3D_PROJECT_DB_OK); + std::vector changed = encoded; + changed.back() ^= 1u; + snapshot.progress = 50; + durable.request = changed.data(); + CHECK(lardon3d_project_db_record_acquisition_campaign_task( + database, &snapshot, LARDON3D_ACQUISITION_CAMPAIGN_TASK_KIND, + LARDON3D_ACQUISITION_CAMPAIGN_TASK_KIND_VERSION, nullptr, &durable, + 2) == LARDON3D_PROJECT_DB_CONSTRAINT); + Lardon3DProjectDbTask generic{}; + CHECK(lardon3d_project_db_load_task(database, 1, &generic) == + LARDON3D_PROJECT_DB_OK && + generic.progress == 0); + snapshot.progress = 0; + durable.request = encoded.data(); + Lardon3DProjectDbCapture first{}, wrong{}, second{}; + CHECK(lardon3d_project_db_create_capture(database, target.scanset_id, 1, + &first) == LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_project_db_create_capture(database, other.scanset_id, 1, + &wrong) == LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_project_db_create_capture(database, target.scanset_id, 1, + &second) == LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_project_db_retain_acquisition_campaign_capture( + database, 1, 1, wrong.capture_id, 1) == + LARDON3D_PROJECT_DB_CONSTRAINT); + CHECK(lardon3d_project_db_retain_acquisition_campaign_capture( + database, 1, 2, second.capture_id, 2) == + LARDON3D_PROJECT_DB_CONSTRAINT); + CHECK(lardon3d_project_db_retain_acquisition_campaign_capture( + database, 1, 1, first.capture_id, 1) == LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_project_db_retain_acquisition_campaign_capture( + database, 1, 1, first.capture_id, 1) == LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_project_db_retain_acquisition_campaign_capture( + database, 1, 1, second.capture_id, 1) == + LARDON3D_PROJECT_DB_CONSTRAINT); + std::vector loaded(encoded.size()); + Lardon3DProjectDbAcquisitionCampaignTask loaded_task{}; + CHECK(lardon3d_project_db_load_acquisition_campaign_task( + database, 1, loaded.data(), loaded.size(), &loaded_task) == + LARDON3D_PROJECT_DB_OK); + CHECK(loaded_task.next_group_id == 1 && loaded_task.group_count == 2 && + loaded_task.request_size == encoded.size() && + std::memcmp(loaded.data(), encoded.data(), encoded.size()) == 0); + std::vector overlong_project_path(LARDON3D_APP_STATE_PATH_CAPACITY + 1, + 'x'); + overlong_project_path.back() = '\0'; + Lardon3DTaskReconstructionContext reconstruction{ + overlong_project_path.data(), database, nullptr, nullptr}; + Lardon3DTaskKindBinding binding{}; + CHECK(!lardon3d_acquisition_campaign_task_reconstruct( + &snapshot, &reconstruction, &binding)); + lardon3d_project_db_close(database); + CHECK(unlink(database_path) == 0); + return 0; +} diff --git a/tests/test_geometric_verification.c b/tests/test_geometric_verification.c index 1877dc3..614ed46 100644 --- a/tests/test_geometric_verification.c +++ b/tests/test_geometric_verification.c @@ -202,7 +202,7 @@ static bool test_model_api(const char *path) { Lardon3DProjectDb *database = NULL; CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); Parents parents; CHECK(create_parents(database, &parents)); @@ -486,7 +486,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) { 0)); CHECK(lardon3d_project_db_open(v11_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); Parents parents; CHECK(create_parents(database, &parents)); unsigned char fingerprint[32] = {0x91}; @@ -501,7 +501,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) { migrated_result.geometric_verification_result_id; lardon3d_project_db_close(database); CHECK(query_integer( - v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 19)); + v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 20)); CHECK( query_integer(v11_path, "SELECT count(*) FROM sqlite_master WHERE type='index' AND " @@ -530,7 +530,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) { 0)); CHECK(lardon3d_project_db_open(failed_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); return true; } diff --git a/tests/test_geometric_verifier_task.c b/tests/test_geometric_verifier_task.c index b4e41ca..722d857 100644 --- a/tests/test_geometric_verifier_task.c +++ b/tests/test_geometric_verifier_task.c @@ -369,7 +369,7 @@ static bool run_task_test(void) { 0)); CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); CHECK(remove_tree(root)); return true; diff --git a/tests/test_match_result.c b/tests/test_match_result.c index 695fa41..cb56734 100644 --- a/tests/test_match_result.c +++ b/tests/test_match_result.c @@ -124,7 +124,7 @@ static bool run_test(void) { Lardon3DProjectDb *database = NULL; CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(database && lardon3d_project_db_schema_version(database) == 19); + CHECK(database && lardon3d_project_db_schema_version(database) == 20); Lardon3DProjectDbScanSet scanset; CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) == @@ -501,7 +501,7 @@ static bool run_test(void) { CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); /* Verify persistence: load previously created results */ CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) == @@ -544,11 +544,11 @@ static bool run_test(void) { v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9)); CHECK(lardon3d_project_db_open(v9_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; CHECK(query_integer( - v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 19)); + v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 20)); CHECK( query_integer(v9_path, "SELECT count(*) FROM sqlite_master WHERE type='table' AND " @@ -576,7 +576,7 @@ static bool run_test(void) { 0)); CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) == LARDON3D_PROJECT_DB_OK && - lardon3d_project_db_schema_version(database) == 19); + lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; diff --git a/tests/test_matcher_task.c b/tests/test_matcher_task.c index 40f1857..7e7954d 100644 --- a/tests/test_matcher_task.c +++ b/tests/test_matcher_task.c @@ -373,7 +373,7 @@ static bool run_test(void) { "name='matcher_tasks'", 0)); CHECK(reopen_runtime(&fixture)); - CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 19); + CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 20); CHECK( query_integer(database_path, "SELECT count(*) FROM sqlite_master WHERE type='table' AND " diff --git a/tests/test_project_db.c b/tests/test_project_db.c index 4c61f95..97c9294 100644 --- a/tests/test_project_db.c +++ b/tests/test_project_db.c @@ -116,7 +116,7 @@ static bool create_future_database(const char *path) { sqlite3_exec( connection, "CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);" - "INSERT INTO metadata VALUES('schema_version',20);", + "INSERT INTO metadata VALUES('schema_version',21);", NULL, NULL, NULL) == SQLITE_OK; return sqlite3_close(connection) == SQLITE_OK && ok; } @@ -940,7 +940,7 @@ static bool run_test(void) { Lardon3DProjectDb *database = NULL; CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(database && lardon3d_project_db_schema_version(database) == 19); + CHECK(database && lardon3d_project_db_schema_version(database) == 20); bool legacy_pending = true; CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) == LARDON3D_PROJECT_DB_OK && @@ -1499,7 +1499,7 @@ static bool run_test(void) { database = NULL; CHECK(query_integer(database_path, "SELECT value FROM metadata WHERE key='schema_version'", - 19)); + 20)); CHECK(query_integer(database_path, "SELECT count(*) FROM tasks WHERE task_id=1", 1)); @@ -1521,12 +1521,13 @@ static bool run_test(void) { "created_at) VALUES(1,1,1,1,1,zeroblob(32),1,2,1,0,0,1);" "DROP TABLE incremental_reconstruction_tasks;" "DROP TABLE incremental_reconstructions;" + "DROP TABLE acquisition_campaign_captures;DROP TABLE acquisition_campaign_tasks;" "DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;" "DROP TABLE capture_images;DROP TABLE captures;" "UPDATE metadata SET value=17 WHERE key='schema_version';")); CHECK(lardon3d_project_db_open(v17_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; CHECK(query_integer(v17_path, @@ -1550,6 +1551,7 @@ static bool run_test(void) { database = NULL; CHECK(execute_test_sql( failed_v19_path, + "DROP TABLE acquisition_campaign_captures;DROP TABLE acquisition_campaign_tasks;" "DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;" "DROP TABLE capture_images;DROP TABLE captures;" "UPDATE metadata SET value=18 WHERE key='schema_version';")); @@ -1564,13 +1566,14 @@ static bool run_test(void) { "name='captures'", 0)); CHECK(lardon3d_project_db_open(failed_v19_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; CHECK(execute_test_sql( failed_v18_path, "DROP TABLE incremental_reconstruction_tasks;" "DROP TABLE incremental_reconstructions;" + "DROP TABLE acquisition_campaign_captures;DROP TABLE acquisition_campaign_tasks;" "DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;" "DROP TABLE capture_images;DROP TABLE captures;" "UPDATE metadata SET value=17 WHERE key='schema_version';")); @@ -1615,7 +1618,7 @@ static bool run_test(void) { CHECK(create_v1_database(legacy_path)); CHECK(lardon3d_project_db_open(legacy_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); CHECK(lardon3d_project_db_get_project(database, &loaded_project) == LARDON3D_PROJECT_DB_OK && strcmp(loaded_project.stable_id, "legacy-project") == 0); @@ -1630,7 +1633,7 @@ static bool run_test(void) { database = NULL; CHECK(query_integer(legacy_path, "SELECT value FROM metadata WHERE key='schema_version'", - 19)); + 20)); CHECK(create_v1_database(failed_migration_path)); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0); @@ -1661,7 +1664,7 @@ static bool run_test(void) { lardon3d_project_db_close(database); database = NULL; CHECK(query_integer( - v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 19)); + v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 20)); CHECK(create_v2_database(failed_v3_migration_path)); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3", "1", 1) == 0); @@ -1695,7 +1698,7 @@ static bool run_test(void) { lardon3d_project_db_close(database); database = NULL; CHECK(query_integer( - v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 19)); + v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 20)); CHECK(create_v3_database(failed_v4_path)); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4", "1", 1) == 0); @@ -1717,7 +1720,7 @@ static bool run_test(void) { fprintf(stderr, "Migration v4 (%d): %s\n", (int)v4_result, error); } CHECK(v4_result == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact", @@ -1772,7 +1775,7 @@ static bool run_test(void) { error); } CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK && - lardon3d_project_db_schema_version(database) == 19); + lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; @@ -1782,7 +1785,7 @@ static bool run_test(void) { 5)); CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) == LARDON3D_PROJECT_DB_OK && - lardon3d_project_db_schema_version(database) == 19); + lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; @@ -1791,11 +1794,11 @@ static bool run_test(void) { v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7)); CHECK(lardon3d_project_db_open(v8_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; CHECK(query_integer( - v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 19)); + v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 20)); CHECK(create_v7_database(failed_v8_path)); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8", "1", 1) == 0); @@ -1812,7 +1815,7 @@ static bool run_test(void) { 0)); CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) == LARDON3D_PROJECT_DB_OK && - lardon3d_project_db_schema_version(database) == 19); + lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; @@ -1831,11 +1834,11 @@ static bool run_test(void) { 0)); CHECK(lardon3d_project_db_open(v10_path, &database, error) == LARDON3D_PROJECT_DB_OK && - lardon3d_project_db_schema_version(database) == 19); + lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; CHECK(query_integer( - v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 19)); + v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 20)); CHECK( query_integer(v10_path, "SELECT count(*) FROM sqlite_master WHERE type='table' AND " @@ -1862,7 +1865,7 @@ static bool run_test(void) { 1)); CHECK(lardon3d_project_db_open(failed_v11_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; @@ -1876,11 +1879,11 @@ static bool run_test(void) { 0)); CHECK(lardon3d_project_db_open(v13_path, &database, error) == LARDON3D_PROJECT_DB_OK && - lardon3d_project_db_schema_version(database) == 19); + lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; CHECK(query_integer( - v13_path, "SELECT value FROM metadata WHERE key='schema_version'", 19)); + v13_path, "SELECT value FROM metadata WHERE key='schema_version'", 20)); CHECK( query_integer(v13_path, "SELECT count(*) FROM sqlite_master WHERE type='table' AND " @@ -1903,7 +1906,7 @@ static bool run_test(void) { true_v14_path); } CHECK(true_v14_result == LARDON3D_PROJECT_DB_OK && - lardon3d_project_db_schema_version(database) == 19); + lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; CHECK( @@ -2018,7 +2021,7 @@ static bool run_test(void) { if (true_v15_result != LARDON3D_PROJECT_DB_OK) fprintf(stderr, "true v15 upgrade: %d %s\n", true_v15_result, error); CHECK(true_v15_result == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); Lardon3DProjectDbProject migrated_project; CHECK(lardon3d_project_db_get_project(database, &migrated_project) == LARDON3D_PROJECT_DB_OK && @@ -2064,7 +2067,7 @@ static bool run_test(void) { database = NULL; CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; CHECK(schema_compare(database_path, true_v15_path, true)); @@ -2099,7 +2102,7 @@ static bool run_test(void) { 0)); CHECK(lardon3d_project_db_open(true_v15_path, &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; @@ -2118,7 +2121,7 @@ static bool run_test(void) { 0)); CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) == LARDON3D_PROJECT_DB_OK && - lardon3d_project_db_schema_version(database) == 19); + lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; @@ -2137,7 +2140,7 @@ static bool run_test(void) { 0)); CHECK(lardon3d_project_db_open(failed_v15_path, &database, error) == LARDON3D_PROJECT_DB_OK && - lardon3d_project_db_schema_version(database) == 19); + lardon3d_project_db_schema_version(database) == 20); lardon3d_project_db_close(database); database = NULL; diff --git a/tests/test_sparse_sfm_model.c b/tests/test_sparse_sfm_model.c index cd59011..976dbd4 100644 --- a/tests/test_sparse_sfm_model.c +++ b/tests/test_sparse_sfm_model.c @@ -123,7 +123,7 @@ static bool run_test(void) { Lardon3DProjectDb *db = NULL; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(db) == 19); + CHECK(lardon3d_project_db_schema_version(db) == 20); Lardon3DProjectDbScanSet scanset; CHECK(lardon3d_project_db_create_scanset(db, "Sparse model", &scanset) == diff --git a/tests/test_sparse_sfm_payload.cpp b/tests/test_sparse_sfm_payload.cpp index 723cdb3..d3d86c4 100644 --- a/tests/test_sparse_sfm_payload.cpp +++ b/tests/test_sparse_sfm_payload.cpp @@ -66,7 +66,7 @@ int main() { Lardon3DProjectDb *database = nullptr; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{}; CHECK(lardon3d_project_db_open(path.c_str(), &database, error) == LARDON3D_PROJECT_DB_OK); - CHECK(lardon3d_project_db_schema_version(database) == 19); + CHECK(lardon3d_project_db_schema_version(database) == 20); sqlite3 *raw = nullptr; CHECK(sqlite3_open(path.c_str(), &raw) == SQLITE_OK);