diff options
-rw-r--r-- | source/blender/blenkernel/BKE_asset_catalog.hh | 28 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_asset_library.hh | 9 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/asset_catalog.cc | 62 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/asset_catalog_test.cc | 165 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/asset_library.cc | 42 |
5 files changed, 298 insertions, 8 deletions
diff --git a/source/blender/blenkernel/BKE_asset_catalog.hh b/source/blender/blenkernel/BKE_asset_catalog.hh index 07373caf701..7b54d7cf572 100644 --- a/source/blender/blenkernel/BKE_asset_catalog.hh +++ b/source/blender/blenkernel/BKE_asset_catalog.hh @@ -74,6 +74,25 @@ class AssetCatalogService { bool write_to_disk(const CatalogFilePath &directory_for_new_files); /** + * Write the catalog definitions to disk in response to the blend file being saved. + * + * The location where the catalogs are saved is variable, and depends on the location of the + * blend file. The first matching rule wins: + * + * - Already loaded a CDF from disk? + * -> Always write to that file. + * - The directory containing the blend file has a blender_assets.cats.txt file? + * -> Merge with & write to that file. + * - The directory containing the blend file is part of an asset library, as per + * the user's preferences? + * -> Merge with & write to ${ASSET_LIBRARY_ROOT}/blender_assets.cats.txt + * - Create a new file blender_assets.cats.txt next to the blend file. + * + * Return true on success, which either means there were no in-memory categories to save, + * or the save was successful. */ + bool write_to_disk_on_blendfile_save(const char *blend_file_path); + + /** * Merge on-disk changes into the in-memory asset catalogs. * This should be called before writing the asset catalogs to disk. * @@ -124,6 +143,15 @@ class AssetCatalogService { std::unique_ptr<AssetCatalogDefinitionFile> construct_cdf_in_memory( const CatalogFilePath &file_path); + /** + * Find a suitable path to write a CDF to. + * + * This depends on the location of the blend file, and on whether a CDF already exists next to it + * or whether the blend file is saved inside an asset library. + */ + static CatalogFilePath find_suitable_cdf_path_for_writing( + const CatalogFilePath &blend_file_path); + std::unique_ptr<AssetCatalogTree> read_into_tree(); void rebuild_tree(); }; diff --git a/source/blender/blenkernel/BKE_asset_library.hh b/source/blender/blenkernel/BKE_asset_library.hh index 68f7481574e..fc5e137dd3e 100644 --- a/source/blender/blenkernel/BKE_asset_library.hh +++ b/source/blender/blenkernel/BKE_asset_library.hh @@ -27,6 +27,7 @@ #include "BKE_asset_library.h" #include "BKE_asset_catalog.hh" +#include "BKE_callbacks.h" #include <memory> @@ -36,6 +37,14 @@ struct AssetLibrary { std::unique_ptr<AssetCatalogService> catalog_service; void load(StringRefNull library_root_directory); + + void on_save_handler_register(); + void on_save_handler_unregister(); + + void on_save_post(struct Main *, struct PointerRNA **pointers, const int num_pointers); + + private: + bCallbackFuncStore on_save_callback_store_; }; } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc index b65ae12e5a7..ee22e25a6ab 100644 --- a/source/blender/blenkernel/intern/asset_catalog.cc +++ b/source/blender/blenkernel/intern/asset_catalog.cc @@ -19,11 +19,14 @@ */ #include "BKE_asset_catalog.hh" +#include "BKE_preferences.h" #include "BLI_fileops.h" #include "BLI_path_util.h" #include "BLI_string_ref.hh" +#include "DNA_userdef_types.h" + /* For S_ISREG() and S_ISDIR() on Windows. */ #ifdef WIN32 # include "BLI_winstuff.h" @@ -266,6 +269,65 @@ bool AssetCatalogService::write_to_disk(const CatalogFilePath &directory_for_new return catalog_definition_file_->write_to_disk(); } +bool AssetCatalogService::write_to_disk_on_blendfile_save(const char *blend_file_path) +{ + /* TODO(Sybren): deduplicate this and write_to_disk(...); maybe the latter function isn't even + * necessary any more. */ + + /* - Already loaded a CDF from disk? -> Always write to that file. */ + if (this->catalog_definition_file_) { + merge_from_disk_before_writing(); + return catalog_definition_file_->write_to_disk(); + } + + if (catalogs_.is_empty() && deleted_catalogs_.is_empty()) { + /* Avoid saving anything, when there is nothing to save. */ + return true; /* Writing nothing when there is nothing to write is still a success. */ + } + + const CatalogFilePath cdf_path_to_write = find_suitable_cdf_path_for_writing(blend_file_path); + this->catalog_definition_file_ = construct_cdf_in_memory(cdf_path_to_write); + merge_from_disk_before_writing(); + return catalog_definition_file_->write_to_disk(); +} + +CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing( + const CatalogFilePath &blend_file_path) +{ + /* Determine the default CDF path in the same directory of the blend file. */ + char blend_dir_path[PATH_MAX]; + BLI_split_dir_part(blend_file_path.c_str(), blend_dir_path, sizeof(blend_dir_path)); + const CatalogFilePath cdf_path_next_to_blend = asset_definition_default_file_path_from_dir( + blend_dir_path); + + if (BLI_exists(cdf_path_next_to_blend.c_str())) { + /* - The directory containing the blend file has a blender_assets.cats.txt file? + * -> Merge with & write to that file. */ + return cdf_path_next_to_blend; + } + + const bUserAssetLibrary *asset_lib_pref = BKE_preferences_asset_library_containing_path( + &U, blend_file_path.c_str()); + if (asset_lib_pref) { + /* - The directory containing the blend file is part of an asset library, as per + * the user's preferences? + * -> Merge with & write to ${ASSET_LIBRARY_ROOT}/blender_assets.cats.txt */ + + char asset_lib_cdf_path[PATH_MAX]; + BLI_path_join(asset_lib_cdf_path, + sizeof(asset_lib_cdf_path), + asset_lib_pref->path, + DEFAULT_CATALOG_FILENAME.c_str(), + NULL); + + return asset_lib_cdf_path; + } + + /* - Otherwise + * -> Create a new file blender_assets.cats.txt next to the blend file. */ + return cdf_path_next_to_blend; +} + std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::construct_cdf_in_memory( const CatalogFilePath &file_path) { diff --git a/source/blender/blenkernel/intern/asset_catalog_test.cc b/source/blender/blenkernel/intern/asset_catalog_test.cc index 87ee61b015f..5177abd2820 100644 --- a/source/blender/blenkernel/intern/asset_catalog_test.cc +++ b/source/blender/blenkernel/intern/asset_catalog_test.cc @@ -19,10 +19,13 @@ #include "BKE_appdir.h" #include "BKE_asset_catalog.hh" +#include "BKE_preferences.h" #include "BLI_fileops.h" #include "BLI_path_util.h" +#include "DNA_userdef_types.h" + #include "testing/testing.h" namespace blender::bke::tests { @@ -43,6 +46,8 @@ const bUUID UUID_AGENT_47("c5744ba5-43f5-4f73-8e52-010ad4a61b34"); /* Subclass that adds accessors such that protected fields can be used in tests. */ class TestableAssetCatalogService : public AssetCatalogService { public: + TestableAssetCatalogService() = default; + explicit TestableAssetCatalogService(const CatalogFilePath &asset_library_root) : AssetCatalogService(asset_library_root) { @@ -405,6 +410,152 @@ TEST_F(AssetCatalogTest, no_writing_empty_files) EXPECT_FALSE(BLI_exists(default_cdf_path.c_str())); } +/* Already loaded a CDF, saving to some unrelated directory. */ +TEST_F(AssetCatalogTest, on_blendfile_save__with_existing_cdf) +{ + const CatalogFilePath top_level_dir = create_temp_path(); // Has trailing slash. + + /* Create a copy of the CDF in SVN, so we can safely write to it. */ + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath cdf_dirname = top_level_dir + "other_dir/"; + const CatalogFilePath cdf_filename = cdf_dirname + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + ASSERT_TRUE(BLI_dir_create_recursive(cdf_dirname.c_str())); + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), cdf_filename.c_str())) + << "Unable to copy " << original_cdf_file << " to " << cdf_filename; + + /* Load the CDF, add a catalog, and trigger a write. This should write to the loaded CDF. */ + TestableAssetCatalogService service(cdf_filename); + service.load_from_disk(); + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + const CatalogFilePath blendfilename = top_level_dir + "subdir/some_file.blend"; + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str())); + EXPECT_EQ(cdf_filename, service.get_catalog_definition_file()->file_path); + + /* Test that the CDF was created in the expected location. */ + const CatalogFilePath backup_filename = cdf_filename + "~"; + EXPECT_TRUE(BLI_exists(cdf_filename.c_str())); + EXPECT_TRUE(BLI_exists(backup_filename.c_str())) + << "Overwritten CDF should have been backed up."; + + /* Test that the on-disk CDF contains the expected catalogs. */ + AssetCatalogService loaded_service(cdf_filename); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)) + << "Expected to see the newly-created catalog."; + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)) + << "Expected to see the already-existing catalog."; +} + +/* Create some catalogs in memory, save to directory that doesn't contain anything else. */ +TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_empty_directory) +{ + const CatalogFilePath target_dir = create_temp_path(); // Has trailing slash. + + TestableAssetCatalogService service; + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + const CatalogFilePath blendfilename = target_dir + "some_file.blend"; + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str())); + + /* Test that the CDF was created in the expected location. */ + const CatalogFilePath expected_cdf_path = target_dir + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + EXPECT_TRUE(BLI_exists(expected_cdf_path.c_str())); + + /* Test that the in-memory CDF has been created, and contains the expected catalog. */ + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf); + EXPECT_TRUE(cdf->contains(cat->catalog_id)); + + /* Test that the on-disk CDF contains the expected catalog. */ + AssetCatalogService loaded_service(expected_cdf_path); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)); +} + +/* Create some catalogs in memory, save to directory that contains a default CDF. */ +TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_merge) +{ + const CatalogFilePath target_dir = create_temp_path(); // Has trailing slash. + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath writable_cdf_file = target_dir + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); + + /* Create the catalog service without loading the already-existing CDF. */ + TestableAssetCatalogService service; + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + /* Mock that the blend file is written to a subdirectory of the asset library. */ + const CatalogFilePath blendfilename = target_dir + "some_file.blend"; + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str())); + + /* Test that the CDF still exists in the expected location. */ + const CatalogFilePath backup_filename = writable_cdf_file + "~"; + EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str())); + EXPECT_TRUE(BLI_exists(backup_filename.c_str())) + << "Overwritten CDF should have been backed up."; + + /* Test that the in-memory CDF has the expected file path. */ + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf); + EXPECT_EQ(writable_cdf_file, cdf->file_path); + + /* Test that the in-memory catalogs have been merged with the on-disk one. */ + AssetCatalogService loaded_service(writable_cdf_file); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); +} + +/* Create some catalogs in memory, save to subdirectory of a registered asset library. */ +TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_asset_lib) +{ + const CatalogFilePath target_dir = create_temp_path(); // Has trailing slash. + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath registered_asset_lib = target_dir + "my_asset_library/"; + CatalogFilePath writable_cdf_file = registered_asset_lib + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + BLI_path_slash_native(writable_cdf_file.data()); + + /* Set up a temporary asset library for testing. */ + bUserAssetLibrary *asset_lib_pref = BKE_preferences_asset_library_add( + &U, "Test", registered_asset_lib.c_str()); + ASSERT_NE(nullptr, asset_lib_pref); + ASSERT_TRUE(BLI_dir_create_recursive(registered_asset_lib.c_str())); + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); + + /* Create the catalog service without loading the already-existing CDF. */ + TestableAssetCatalogService service; + const CatalogFilePath blenddirname = registered_asset_lib + "subdirectory/"; + const CatalogFilePath blendfilename = blenddirname + "some_file.blend"; + ASSERT_TRUE(BLI_dir_create_recursive(blenddirname.c_str())); + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + /* Mock that the blend file is written to the directory already containing a CDF. */ + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str())); + + /* Test that the CDF still exists in the expected location. */ + EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str())); + const CatalogFilePath backup_filename = writable_cdf_file + "~"; + EXPECT_TRUE(BLI_exists(backup_filename.c_str())) + << "Overwritten CDF should have been backed up."; + + /* Test that the in-memory CDF has the expected file path. */ + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + BLI_path_slash_native(cdf->file_path.data()); + EXPECT_EQ(writable_cdf_file, cdf->file_path); + + /* Test that the in-memory catalogs have been merged with the on-disk one. */ + AssetCatalogService loaded_service(writable_cdf_file); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + + BKE_preferences_asset_library_remove(&U, asset_lib_pref); +} + TEST_F(AssetCatalogTest, create_first_catalog_from_scratch) { /* Even from scratch a root directory should be known. */ @@ -450,7 +601,7 @@ TEST_F(AssetCatalogTest, create_catalog_after_loading_file) AssetCatalogService::DEFAULT_CATALOG_FILENAME; const CatalogFilePath writable_catalog_path = temp_lib_root + AssetCatalogService::DEFAULT_CATALOG_FILENAME; - BLI_copy(default_catalog_path.c_str(), writable_catalog_path.c_str()); + ASSERT_EQ(0, BLI_copy(default_catalog_path.c_str(), writable_catalog_path.c_str())); EXPECT_TRUE(BLI_is_dir(temp_lib_root.c_str())); EXPECT_TRUE(BLI_is_file(writable_catalog_path.c_str())); @@ -485,8 +636,7 @@ TEST_F(AssetCatalogTest, create_catalog_after_loading_file) TEST_F(AssetCatalogTest, create_catalog_path_cleanup) { - const CatalogFilePath temp_lib_root = use_temp_path(); - AssetCatalogService service(temp_lib_root); + AssetCatalogService service; AssetCatalog *cat = service.create_catalog(" /some/path / "); EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id)); @@ -496,8 +646,7 @@ TEST_F(AssetCatalogTest, create_catalog_path_cleanup) TEST_F(AssetCatalogTest, create_catalog_simple_name) { - const CatalogFilePath temp_lib_root = use_temp_path(); - AssetCatalogService service(temp_lib_root); + AssetCatalogService service; AssetCatalog *cat = service.create_catalog( "production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands"); @@ -568,14 +717,14 @@ TEST_F(AssetCatalogTest, merge_catalog_files) const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; const CatalogFilePath modified_cdf_file = asset_library_root_ + "/modified_assets.cats.txt"; const CatalogFilePath temp_cdf_file = cdf_dir + "blender_assets.cats.txt"; - BLI_copy(original_cdf_file.c_str(), temp_cdf_file.c_str()); + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), temp_cdf_file.c_str())); // Load the unmodified, original CDF. TestableAssetCatalogService service(asset_library_root_); service.load_from_disk(cdf_dir); // Copy a modified file, to mimick a situation where someone changed the CDF after we loaded it. - BLI_copy(modified_cdf_file.c_str(), temp_cdf_file.c_str()); + ASSERT_EQ(0, BLI_copy(modified_cdf_file.c_str(), temp_cdf_file.c_str())); // Overwrite the modified file. This should merge the on-disk file with our catalogs. service.write_to_disk(cdf_dir); @@ -602,7 +751,7 @@ TEST_F(AssetCatalogTest, backups) const CatalogFilePath cdf_dir = create_temp_path(); const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; const CatalogFilePath writable_cdf_file = cdf_dir + "/blender_assets.cats.txt"; - BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str()); + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); /* Read a CDF, modify, and write it. */ AssetCatalogService service(cdf_dir); diff --git a/source/blender/blenkernel/intern/asset_library.cc b/source/blender/blenkernel/intern/asset_library.cc index 1153e7b29f5..6fed355c41b 100644 --- a/source/blender/blenkernel/intern/asset_library.cc +++ b/source/blender/blenkernel/intern/asset_library.cc @@ -19,6 +19,8 @@ */ #include "BKE_asset_library.hh" +#include "BKE_callbacks.h" +#include "BKE_main.h" #include "MEM_guardedalloc.h" @@ -31,6 +33,7 @@ struct AssetLibrary *BKE_asset_library_load(const char *library_path) { blender::bke::AssetLibrary *lib = new blender::bke::AssetLibrary(); + lib->on_save_handler_register(); lib->load(library_path); return reinterpret_cast<struct AssetLibrary *>(lib); } @@ -38,6 +41,7 @@ struct AssetLibrary *BKE_asset_library_load(const char *library_path) void BKE_asset_library_free(struct AssetLibrary *asset_library) { blender::bke::AssetLibrary *lib = reinterpret_cast<blender::bke::AssetLibrary *>(asset_library); + lib->on_save_handler_unregister(); delete lib; } @@ -50,4 +54,42 @@ void AssetLibrary::load(StringRefNull library_root_directory) this->catalog_service = std::move(catalog_service); } +namespace { +void asset_library_on_save_post(struct Main *main, + struct PointerRNA **pointers, + const int num_pointers, + void *arg) +{ + AssetLibrary *asset_lib = static_cast<AssetLibrary *>(arg); + asset_lib->on_save_post(main, pointers, num_pointers); +} +} // namespace + +void AssetLibrary::on_save_handler_register() +{ + /* The callback system doesn't own `on_save_callback_store_`. */ + on_save_callback_store_.alloc = false; + + on_save_callback_store_.func = asset_library_on_save_post; + on_save_callback_store_.arg = this; + + BKE_callback_add(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST); +} + +void AssetLibrary::on_save_handler_unregister() +{ + BKE_callback_remove(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST); +} + +void AssetLibrary::on_save_post(struct Main *main, + struct PointerRNA ** /*pointers*/, + const int /*num_pointers*/) +{ + if (this->catalog_service) { + return; + } + + this->catalog_service->write_to_disk_on_blendfile_save(main->name); +} + } // namespace blender::bke |