diff --git a/src/project_db.c b/src/project_db.c index cddd32e..1242675 100644 --- a/src/project_db.c +++ b/src/project_db.c @@ -2510,6 +2510,80 @@ static const char image_select[] = "a.state,a.created_at,i.producer_task_id,i.imported_at FROM images i JOIN image_assets a ON " "a.asset_id=i.asset_id "; +static Lardon3DProjectDbResult ensure_capture_for_registered_image_locked( + Lardon3DProjectDb *database, uint64_t scanset_id, uint64_t asset_id, uint64_t image_id) { + sqlite3_stmt *statement = NULL; + Lardon3DProjectDbResult result = prepare( + database, "SELECT capture_id FROM capture_images WHERE image_id=?1", &statement); + if (result != LARDON3D_PROJECT_DB_OK) { + return result; + } + (void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)image_id); + int code = sqlite3_step(statement); + if (code == SQLITE_ROW) { + (void)sqlite3_finalize(statement); + return LARDON3D_PROJECT_DB_OK; + } + (void)sqlite3_finalize(statement); + if (code != SQLITE_DONE) { + return sqlite_result(database, code, "find capture for registered image"); + } + + result = prepare( + database, "INSERT INTO captures(scanset_id,created_at) " + "SELECT scanset_id,imported_at FROM images WHERE image_id=?1 AND scanset_id=?2", + &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + (void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)image_id); + (void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)scanset_id); + result = step_done(database, statement, "create initial image capture"); + statement = NULL; + } + if (result != LARDON3D_PROJECT_DB_OK) { + return result; + } + if (sqlite3_changes(database->connection) != 1) { + return LARDON3D_PROJECT_DB_CORRUPT; + } + sqlite3_int64 capture_id = sqlite3_last_insert_rowid(database->connection); + if (capture_id <= 0) { + return LARDON3D_PROJECT_DB_CORRUPT; + } + + result = prepare(database, + "INSERT INTO capture_assets(capture_id,asset_id,role) VALUES(?1,?2,1)", + &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + (void)sqlite3_bind_int64(statement, 1, capture_id); + (void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)asset_id); + result = step_done(database, statement, "attach initial image source asset"); + statement = NULL; + } + if (result == LARDON3D_PROJECT_DB_OK) { + result = prepare(database, + "INSERT INTO capture_images(capture_id,image_id) VALUES(?1,?2)", + &statement); + } + if (result == LARDON3D_PROJECT_DB_OK) { + (void)sqlite3_bind_int64(statement, 1, capture_id); + (void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)image_id); + result = step_done(database, statement, "attach initial image capture"); + statement = NULL; + } + if (result == LARDON3D_PROJECT_DB_OK) { + result = prepare(database, + "INSERT INTO capture_selections(capture_id,image_id) VALUES(?1,?2)", + &statement); + } + if (result == LARDON3D_PROJECT_DB_OK) { + (void)sqlite3_bind_int64(statement, 1, capture_id); + (void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)image_id); + result = step_done(database, statement, "select initial capture image"); + statement = NULL; + } + return result; +} + Lardon3DProjectDbResult lardon3d_project_db_register_image_asset( Lardon3DProjectDb *database, const unsigned char sha256[LARDON3D_PROJECT_DB_SHA256_SIZE], const char *asset_path, @@ -2682,6 +2756,10 @@ Lardon3DProjectDbResult lardon3d_project_db_register_image( (void)sqlite3_finalize(statement); statement = NULL; } + if (result == LARDON3D_PROJECT_DB_OK) { + result = ensure_capture_for_registered_image_locked(database, scanset_id, (uint64_t)asset_id, + (uint64_t)image_id); + } if (result == LARDON3D_PROJECT_DB_OK) { result = execute(database, "COMMIT", "commit image register"); } @@ -6346,12 +6424,14 @@ lardon3d_project_db_test_delete_catalog_identity(Lardon3DProjectDb *database, ui Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin catalog identity deletion test"); const char *sql[] = { + ("DELETE FROM captures WHERE capture_id IN " + "(SELECT capture_id FROM capture_images WHERE image_id=?1)"), "DELETE FROM images WHERE image_id=?1", "DELETE FROM image_assets WHERE asset_id=?1", "DELETE FROM scansets WHERE scanset_id=?1", }; - const uint64_t ids[] = {image_id, asset_id, scanset_id}; - for (size_t index = 0; index < 3 && result == LARDON3D_PROJECT_DB_OK; ++index) { + const uint64_t ids[] = {image_id, image_id, asset_id, scanset_id}; + for (size_t index = 0; index < 4 && result == LARDON3D_PROJECT_DB_OK; ++index) { sqlite3_stmt *statement = NULL; result = prepare(database, sql[index], &statement); if (result == LARDON3D_PROJECT_DB_OK) { diff --git a/tests/test_persistent_image_catalog.c b/tests/test_persistent_image_catalog.c index 06cf8d1..d2bc871 100644 --- a/tests/test_persistent_image_catalog.c +++ b/tests/test_persistent_image_catalog.c @@ -114,6 +114,26 @@ import_thread(void *userdata) return NULL; } +static bool +has_initial_capture(Lardon3DProjectDb *database, const Lardon3DProjectDbImage *image, + const Lardon3DProjectDbImageAsset *asset, uint64_t scanset_id, + Lardon3DProjectDbCapture *capture) +{ + if (lardon3d_project_db_find_capture_for_image(database, image->image_id, capture) + != LARDON3D_PROJECT_DB_OK + || capture->scanset_id != scanset_id) return false; + uint64_t selected_image = 0; + if (lardon3d_project_db_get_selected_capture_image(database, capture->capture_id, + &selected_image) != LARDON3D_PROJECT_DB_OK + || selected_image != image->image_id) return false; + Lardon3DProjectDbCaptureAsset capture_asset; + size_t count = 0; + return lardon3d_project_db_list_capture_assets(database, capture->capture_id, 0, + &capture_asset, 1, &count) == LARDON3D_PROJECT_DB_OK + && count == 1 && capture_asset.asset_id == asset->asset_id + && capture_asset.role == LARDON3D_DB_CAPTURE_ASSET_SOURCE; +} + static bool run_test(void) { @@ -139,6 +159,9 @@ run_test(void) CHECK(lardon3d_image_catalog_create_scanset(&state, "Campagne générale", &a)); CHECK(lardon3d_image_catalog_create_scanset(&state, "Pièce démontée", &b)); CHECK(a.scanset_id != b.scanset_id); + Lardon3DProjectDbCapture preexisting_capture; + CHECK(lardon3d_project_db_create_capture(database, a.scanset_id, 1, + &preexisting_capture) == LARDON3D_PROJECT_DB_OK); Lardon3DProjectDbScanSet scanset_page[2]; size_t scanset_count = 0; CHECK(lardon3d_project_db_list_scansets(database, 0, scanset_page, 1, &scanset_count) == LARDON3D_PROJECT_DB_OK && scanset_count == 1 @@ -163,14 +186,20 @@ run_test(void) CHECK(lardon3d_image_catalog_import_file(&state, a.scanset_id, source_a, 0, &image_a, &asset_a) == LARDON3D_IMAGE_CATALOG_IMPORTED); CHECK(canonical_asset_path(asset_a.path)); + Lardon3DProjectDbCapture capture_a, capture_b, capture_different; + CHECK(has_initial_capture(database, &image_a, &asset_a, a.scanset_id, &capture_a)); + CHECK(capture_a.capture_id != image_a.image_id); CHECK(lardon3d_image_catalog_import_file(&state, a.scanset_id, source_b, 0, &duplicate_a, &duplicate_asset) == LARDON3D_IMAGE_CATALOG_ALREADY_PRESENT); CHECK(image_a.image_id == duplicate_a.image_id && asset_a.asset_id == duplicate_asset.asset_id); + CHECK(has_initial_capture(database, &duplicate_a, &duplicate_asset, a.scanset_id, &capture_a)); CHECK(lardon3d_image_catalog_import_file(&state, b.scanset_id, source_b, 0, &image_b, &asset_b) == LARDON3D_IMAGE_CATALOG_IMPORTED); CHECK(image_b.image_id != image_a.image_id && asset_b.asset_id == asset_a.asset_id); CHECK(strcmp(asset_b.path, asset_a.path) == 0); + CHECK(has_initial_capture(database, &image_b, &asset_b, b.scanset_id, &capture_b)); + CHECK(capture_b.capture_id != capture_a.capture_id); char different_dir[PATH_MAX], same_name[PATH_MAX]; CHECK(join_path(different_dir, root, "different") && mkdir(different_dir, 0700) == 0); @@ -184,6 +213,9 @@ run_test(void) && different_asset.asset_id != asset_a.asset_id && canonical_asset_path(different_asset.path) && strcmp(different_asset.path, asset_a.path) != 0); + CHECK(has_initial_capture(database, &different_image, &different_asset, a.scanset_id, + &capture_different)); + CHECK(capture_different.capture_id != capture_a.capture_id); char orphan_source[PATH_MAX], asset_root[PATH_MAX]; CHECK(join_path(orphan_source, root, "orphan.jpg")); diff --git a/tests/test_project_db.c b/tests/test_project_db.c index cdb853e..ae3b8bc 100644 --- a/tests/test_project_db.c +++ b/tests/test_project_db.c @@ -1066,22 +1066,24 @@ static bool run_test(void) { /* S1: Capture is a catalog policy layer; image_id and asset_id stay immutable. */ Lardon3DProjectDbCapture capture, other_capture, mapped_capture; - CHECK(lardon3d_project_db_create_capture(database, replacement_scanset.scanset_id, 20, - &capture) == LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_project_db_find_capture_for_image(database, pair_image.image_id, &capture) == + LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_create_capture(database, replacement_scanset.scanset_id, 21, &other_capture) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_create_capture(database, UINT64_C(999999), 22, &mapped_capture) == LARDON3D_PROJECT_DB_CONSTRAINT); Lardon3DProjectDbCapture capture_page[2]; size_t capture_count = 0; - CHECK(lardon3d_project_db_list_captures(database, replacement_scanset.scanset_id, 0, + CHECK(lardon3d_project_db_list_captures(database, replacement_scanset.scanset_id, + capture.capture_id - 1, capture_page, 1, &capture_count) == LARDON3D_PROJECT_DB_OK && capture_count == 1 && capture_page[0].capture_id == capture.capture_id); uint64_t selected_image = 0; CHECK(lardon3d_project_db_get_selected_capture_image(database, capture.capture_id, &selected_image) == - LARDON3D_PROJECT_DB_NOT_FOUND); + LARDON3D_PROJECT_DB_OK && + selected_image == pair_image.image_id); unsigned char raw_hash[LARDON3D_PROJECT_DB_SHA256_SIZE] = {5}; char raw_asset_path[LARDON3D_PROJECT_DB_PATH_CAPACITY]; asset_path_for_hash(raw_hash, raw_asset_path); @@ -1093,7 +1095,7 @@ static bool run_test(void) { LARDON3D_DB_CAPTURE_ASSET_SOURCE) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_attach_capture_asset( database, capture.capture_id, pair_image.asset_id, - LARDON3D_DB_CAPTURE_ASSET_SOURCE) == LARDON3D_PROJECT_DB_OK); + LARDON3D_DB_CAPTURE_ASSET_SOURCE) == LARDON3D_PROJECT_DB_CONSTRAINT); CHECK(lardon3d_project_db_attach_capture_asset( database, capture.capture_id, raw_asset.asset_id, (Lardon3DProjectDbCaptureAssetRole)99) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); @@ -1108,6 +1110,17 @@ static bool run_test(void) { database, replacement_scanset.scanset_id, developed_hash, developed_asset_path, 3, "developed.png", "/source/developed.png", 0, 23, &identity_status, &developed_image) == LARDON3D_PROJECT_DB_OK); + Lardon3DProjectDbCapture developed_capture; + CHECK(lardon3d_project_db_find_capture_for_image(database, developed_image.image_id, + &developed_capture) == LARDON3D_PROJECT_DB_OK); + lardon3d_project_db_close(database); + database = NULL; + char remove_developed_capture[256]; + CHECK(snprintf(remove_developed_capture, sizeof(remove_developed_capture), + "PRAGMA foreign_keys=ON;DELETE FROM captures WHERE capture_id=%llu", + (unsigned long long)developed_capture.capture_id) > 0); + CHECK(execute_test_sql(database_path, remove_developed_capture)); + CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_attach_capture_asset( database, capture.capture_id, developed_image.asset_id, LARDON3D_DB_CAPTURE_ASSET_DERIVED) == LARDON3D_PROJECT_DB_OK); @@ -1118,7 +1131,7 @@ static bool run_test(void) { LARDON3D_PROJECT_DB_OK && capture_asset_count == 3); CHECK(lardon3d_project_db_attach_capture_image(database, capture.capture_id, - pair_image.image_id) == LARDON3D_PROJECT_DB_OK); + pair_image.image_id) == LARDON3D_PROJECT_DB_CONSTRAINT); CHECK(lardon3d_project_db_attach_capture_image(database, other_capture.capture_id, pair_image.image_id) == LARDON3D_PROJECT_DB_CONSTRAINT); @@ -1141,6 +1154,16 @@ static bool run_test(void) { &selected_image) == LARDON3D_PROJECT_DB_OK && selected_image == developed_image.image_id && pair_image.image_id != developed_image.image_id); + CHECK(lardon3d_project_db_register_image( + database, replacement_scanset.scanset_id, third_hash, third_asset_path, 1, + "pair-a.jpg", "/source/pair-a.jpg", 0, 3, &identity_status, + &pair_image) == LARDON3D_PROJECT_DB_OK && + identity_status == LARDON3D_PROJECT_DB_IMAGE_ALREADY_PRESENT); + CHECK(lardon3d_project_db_get_selected_capture_image(database, capture.capture_id, + &selected_image) == + LARDON3D_PROJECT_DB_OK && + selected_image == developed_image.image_id); + CHECK(query_integer(database_path, "SELECT count(*) FROM asset_derivations", 0)); Lardon3DProjectDbAssetDerivation derivation = { .parent_asset_id = raw_asset.asset_id, .child_asset_id = developed_image.asset_id,