Blender  V3.3
asset_catalog_test.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later
2  * Copyright 2020 Blender Foundation. All rights reserved. */
3 
4 #include "BKE_appdir.h"
5 #include "BKE_asset_catalog.hh"
6 #include "BKE_preferences.h"
7 
8 #include "BLI_fileops.h"
9 #include "BLI_path_util.h"
10 
11 #include "DNA_asset_types.h"
12 #include "DNA_userdef_types.h"
13 
14 #include "CLG_log.h"
15 
16 #include "testing/testing.h"
17 
18 namespace blender::bke::tests {
19 
20 /* UUIDs from tests/data/asset_library/blender_assets.cats.txt */
21 const bUUID UUID_ID_WITHOUT_PATH("e34dd2c5-5d2e-4668-9794-1db5de2a4f71");
22 const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
23 const bUUID UUID_POSES_ELLIE_WHITESPACE("b06132f6-5687-4751-a6dd-392740eb3c46");
24 const bUUID UUID_POSES_ELLIE_TRAILING_SLASH("3376b94b-a28d-4d05-86c1-bf30b937130d");
25 const bUUID UUID_POSES_ELLIE_BACKSLASHES("a51e17ae-34fc-47d5-ba0f-64c2c9b771f7");
26 const bUUID UUID_POSES_RUZENA("79a4f887-ab60-4bd4-94da-d572e27d6aed");
27 const bUUID UUID_POSES_RUZENA_HAND("81811c31-1a88-4bd7-bb34-c6fc2607a12e");
28 const bUUID UUID_POSES_RUZENA_FACE("82162c1f-06cc-4d91-a9bf-4f72c104e348");
29 const bUUID UUID_WITHOUT_SIMPLENAME("d7916a31-6ca9-4909-955f-182ca2b81fa3");
30 const bUUID UUID_ANOTHER_RUZENA("00000000-d9fa-4b91-b704-e6af1f1339ef");
31 
32 /* UUIDs from tests/data/asset_library/modified_assets.cats.txt */
33 const bUUID UUID_AGENT_47("c5744ba5-43f5-4f73-8e52-010ad4a61b34");
34 
35 /* Subclass that adds accessors such that protected fields can be used in tests. */
37  public:
39 
40  explicit TestableAssetCatalogService(const CatalogFilePath &asset_library_root)
41  : AssetCatalogService(asset_library_root)
42  {
43  }
44 
46  {
48  }
49 
51  {
53  }
54 
56  {
58  }
59 
61  {
63  }
64 
66  {
67  int64_t count = 0;
68  for (auto &catalog_uptr : get_catalogs().values()) {
69  if (catalog_uptr->path == path) {
70  count++;
71  }
72  }
73  return count;
74  }
75 };
76 
77 class AssetCatalogTest : public testing::Test {
78  protected:
81 
82  static void SetUpTestSuite()
83  {
84  testing::Test::SetUpTestSuite();
85  CLG_init();
86  }
87 
88  static void TearDownTestSuite()
89  {
90  CLG_exit();
91  testing::Test::TearDownTestSuite();
92  }
93 
94  void SetUp() override
95  {
96  const std::string test_files_dir = blender::tests::flags_test_asset_dir();
97  if (test_files_dir.empty()) {
98  FAIL();
99  }
100 
101  asset_library_root_ = test_files_dir + "/" + "asset_library";
102  temp_library_path_ = "";
103  }
104 
105  void TearDown() override
106  {
107  if (!temp_library_path_.empty()) {
108  BLI_delete(temp_library_path_.c_str(), true, true);
109  temp_library_path_ = "";
110  }
111  }
112 
113  /* Register a temporary path, which will be removed at the end of the test.
114  * The returned path ends in a slash. */
116  {
117  BKE_tempdir_init("");
118  const CatalogFilePath tempdir = BKE_tempdir_session();
119  temp_library_path_ = tempdir + "test-temporary-path/";
120  return temp_library_path_;
121  }
122 
124  {
126  BLI_dir_create_recursive(path.c_str());
127  return path;
128  }
129 
130  void assert_expected_item(const AssetCatalogPath &expected_path,
131  const AssetCatalogTreeItem &actual_item)
132  {
133  if (expected_path != actual_item.catalog_path().str()) {
134  /* This will fail, but with a nicer error message than just calling FAIL(). */
135  EXPECT_EQ(expected_path, actual_item.catalog_path());
136  return;
137  }
138 
139  /* Is the catalog name as expected? "character", "Ellie", ... */
140  EXPECT_EQ(expected_path.name(), actual_item.get_name());
141 
142  /* Does the computed number of parents match? */
143  const std::string expected_path_str = expected_path.str();
144  const size_t expected_parent_count = std::count(
145  expected_path_str.begin(), expected_path_str.end(), AssetCatalogPath::SEPARATOR);
146  EXPECT_EQ(expected_parent_count, actual_item.count_parents());
147  }
148 
154  const std::vector<AssetCatalogPath> &expected_paths)
155  {
156  int i = 0;
157  tree->foreach_item([&](const AssetCatalogTreeItem &actual_item) {
158  ASSERT_LT(i, expected_paths.size())
159  << "More catalogs in tree than expected; did not expect " << actual_item.catalog_path();
160  assert_expected_item(expected_paths[i], actual_item);
161  i++;
162  });
163  }
164 
171  const std::vector<AssetCatalogPath> &expected_paths)
172  {
173  int i = 0;
174  tree->foreach_root_item([&](const AssetCatalogTreeItem &actual_item) {
175  ASSERT_LT(i, expected_paths.size())
176  << "More catalogs in tree root than expected; did not expect "
177  << actual_item.catalog_path();
178  assert_expected_item(expected_paths[i], actual_item);
179  i++;
180  });
181  }
182 
189  const std::vector<AssetCatalogPath> &expected_paths)
190  {
191  int i = 0;
192  parent_item->foreach_child([&](const AssetCatalogTreeItem &actual_item) {
193  ASSERT_LT(i, expected_paths.size())
194  << "More catalogs in tree item than expected; did not expect "
195  << actual_item.catalog_path();
196  assert_expected_item(expected_paths[i], actual_item);
197  i++;
198  });
199  }
200 
201  /* Used by on_blendfile_save__from_memory_into_existing_asset_lib* test functions. */
202  void save_from_memory_into_existing_asset_lib(const bool should_top_level_cdf_exist)
203  {
204  const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */
205  const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
206  const CatalogFilePath registered_asset_lib = target_dir + "my_asset_library/";
207  const CatalogFilePath asset_lib_subdir = registered_asset_lib + "subdir/";
208  CatalogFilePath cdf_toplevel = registered_asset_lib +
210  CatalogFilePath cdf_in_subdir = asset_lib_subdir +
212  BLI_path_slash_native(cdf_toplevel.data());
213  BLI_path_slash_native(cdf_in_subdir.data());
214 
215  /* Set up a temporary asset library for testing. */
217  &U, "Test", registered_asset_lib.c_str());
218  ASSERT_NE(nullptr, asset_lib_pref);
219  ASSERT_TRUE(BLI_dir_create_recursive(asset_lib_subdir.c_str()));
220 
221  if (should_top_level_cdf_exist) {
222  ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), cdf_toplevel.c_str()));
223  }
224 
225  /* Create an empty CDF to add complexity. It should not save to this, but to the top-level
226  * one. */
227  ASSERT_TRUE(BLI_file_touch(cdf_in_subdir.c_str()));
228  ASSERT_EQ(0, BLI_file_size(cdf_in_subdir.c_str()));
229 
230  /* Create the catalog service without loading the already-existing CDF. */
232  const CatalogFilePath blendfilename = asset_lib_subdir + "some_file.blend";
233  const AssetCatalog *cat = service.create_catalog("some/catalog/path");
234 
235  /* Mock that the blend file is written to the directory already containing a CDF. */
236  ASSERT_TRUE(service.write_to_disk(blendfilename));
237 
238  /* Test that the CDF still exists in the expected location. */
239  EXPECT_TRUE(BLI_exists(cdf_toplevel.c_str()));
240  const CatalogFilePath backup_filename = cdf_toplevel + "~";
241  const bool backup_exists = BLI_exists(backup_filename.c_str());
242  EXPECT_EQ(should_top_level_cdf_exist, backup_exists)
243  << "Overwritten CDF should have been backed up.";
244 
245  /* Test that the in-memory CDF has the expected file path. */
247  BLI_path_slash_native(cdf->file_path.data());
248  EXPECT_EQ(cdf_toplevel, cdf->file_path);
249 
250  /* Test that the in-memory catalogs have been merged with the on-disk one. */
251  AssetCatalogService loaded_service(cdf_toplevel);
252  loaded_service.load_from_disk();
253  EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id));
254 
255  /* This catalog comes from a pre-existing CDF that should have been merged.
256  * However, if the file doesn't exist, so does the catalog. */
257  AssetCatalog *poses_ellie_catalog = loaded_service.find_catalog(UUID_POSES_ELLIE);
258  if (should_top_level_cdf_exist) {
259  EXPECT_NE(nullptr, poses_ellie_catalog);
260  }
261  else {
262  EXPECT_EQ(nullptr, poses_ellie_catalog);
263  }
264 
265  /* Test that the "red herring" CDF has not been touched. */
266  EXPECT_EQ(0, BLI_file_size(cdf_in_subdir.c_str()));
267 
268  BKE_preferences_asset_library_remove(&U, asset_lib_pref);
269  }
270 };
271 
272 TEST_F(AssetCatalogTest, load_single_file)
273 {
274  AssetCatalogService service(asset_library_root_);
275  service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
276 
277  /* Test getting a non-existent catalog ID. */
278  EXPECT_EQ(nullptr, service.find_catalog(BLI_uuid_generate_random()));
279 
280  /* Test getting an invalid catalog (without path definition). */
281  AssetCatalog *cat_without_path = service.find_catalog(UUID_ID_WITHOUT_PATH);
282  ASSERT_EQ(nullptr, cat_without_path);
283 
284  /* Test getting a regular catalog. */
285  AssetCatalog *poses_ellie = service.find_catalog(UUID_POSES_ELLIE);
286  ASSERT_NE(nullptr, poses_ellie);
287  EXPECT_EQ(UUID_POSES_ELLIE, poses_ellie->catalog_id);
288  EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str());
289  EXPECT_EQ("POSES_ELLIE", poses_ellie->simple_name);
290 
291  /* Test white-space stripping and support in the path. */
292  AssetCatalog *poses_whitespace = service.find_catalog(UUID_POSES_ELLIE_WHITESPACE);
293  ASSERT_NE(nullptr, poses_whitespace);
295  EXPECT_EQ("character/Ellie/poselib/white space", poses_whitespace->path.str());
296  EXPECT_EQ("POSES_ELLIE WHITESPACE", poses_whitespace->simple_name);
297 
298  /* Test getting a UTF-8 catalog ID. */
299  AssetCatalog *poses_ruzena = service.find_catalog(UUID_POSES_RUZENA);
300  ASSERT_NE(nullptr, poses_ruzena);
301  EXPECT_EQ(UUID_POSES_RUZENA, poses_ruzena->catalog_id);
302  EXPECT_EQ("character/Ružena/poselib", poses_ruzena->path.str());
303  EXPECT_EQ("POSES_RUŽENA", poses_ruzena->simple_name);
304 
305  /* Test getting a catalog that aliases an earlier-defined catalog. */
306  AssetCatalog *another_ruzena = service.find_catalog(UUID_ANOTHER_RUZENA);
307  ASSERT_NE(nullptr, another_ruzena);
308  EXPECT_EQ(UUID_ANOTHER_RUZENA, another_ruzena->catalog_id);
309  EXPECT_EQ("character/Ružena/poselib", another_ruzena->path.str());
310  EXPECT_EQ("Another Ružena", another_ruzena->simple_name);
311 }
312 
313 TEST_F(AssetCatalogTest, load_catalog_path_backslashes)
314 {
315  AssetCatalogService service(asset_library_root_);
316  service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
317 
318  const AssetCatalog *found_by_id = service.find_catalog(UUID_POSES_ELLIE_BACKSLASHES);
319  ASSERT_NE(nullptr, found_by_id);
320  EXPECT_EQ(AssetCatalogPath("character/Ellie/backslashes"), found_by_id->path)
321  << "Backslashes should be normalized when loading from disk.";
322  EXPECT_EQ(StringRefNull("Windows For Life!"), found_by_id->simple_name);
323 
324  const AssetCatalog *found_by_path = service.find_catalog_by_path("character/Ellie/backslashes");
325  EXPECT_EQ(found_by_id, found_by_path)
326  << "Catalog with backslashed path should be findable by the normalized path.";
327 
328  EXPECT_EQ(nullptr, service.find_catalog_by_path("character\\Ellie\\backslashes"))
329  << "Nothing should be found when searching for backslashes.";
330 }
331 
332 TEST_F(AssetCatalogTest, is_first_loaded_flag)
333 {
334  AssetCatalogService service(asset_library_root_);
335  service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
336 
337  AssetCatalog *new_cat = service.create_catalog("never/before/seen/path");
338  EXPECT_FALSE(new_cat->flags.is_first_loaded)
339  << "Adding a catalog at runtime should never mark it as 'first loaded'; "
340  "only loading from disk is allowed to do that.";
341 
342  AssetCatalog *alias_cat = service.create_catalog("character/Ružena/poselib");
343  EXPECT_FALSE(alias_cat->flags.is_first_loaded)
344  << "Adding a new catalog with an already-loaded path should not mark it as 'first loaded'";
345 
346  EXPECT_TRUE(service.find_catalog(UUID_POSES_ELLIE)->flags.is_first_loaded);
348  EXPECT_TRUE(service.find_catalog(UUID_POSES_RUZENA)->flags.is_first_loaded);
349  EXPECT_FALSE(service.find_catalog(UUID_ANOTHER_RUZENA)->flags.is_first_loaded);
350 
351  AssetCatalog *ruzena = service.find_catalog_by_path("character/Ružena/poselib");
353  << "The first-seen definition of a catalog should be returned";
354 }
355 
356 TEST_F(AssetCatalogTest, insert_item_into_tree)
357 {
358  {
360  std::unique_ptr<AssetCatalog> catalog_empty_path = AssetCatalog::from_path("");
361  tree.insert_item(*catalog_empty_path);
362 
363  assert_expected_tree_items(&tree, {});
364  }
365 
366  {
368 
369  std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item");
370  tree.insert_item(*catalog);
371  assert_expected_tree_items(&tree, {"item"});
372 
373  /* Insert child after parent already exists. */
374  std::unique_ptr<AssetCatalog> child_catalog = AssetCatalog::from_path("item/child");
375  tree.insert_item(*catalog);
376  assert_expected_tree_items(&tree, {"item", "item/child"});
377 
378  std::vector<AssetCatalogPath> expected_paths;
379 
380  /* Test inserting multi-component sub-path. */
381  std::unique_ptr<AssetCatalog> grandgrandchild_catalog = AssetCatalog::from_path(
382  "item/child/grandchild/grandgrandchild");
383  tree.insert_item(*catalog);
384  expected_paths = {
385  "item", "item/child", "item/child/grandchild", "item/child/grandchild/grandgrandchild"};
386  assert_expected_tree_items(&tree, expected_paths);
387 
388  std::unique_ptr<AssetCatalog> root_level_catalog = AssetCatalog::from_path("root level");
389  tree.insert_item(*catalog);
390  expected_paths = {"item",
391  "item/child",
392  "item/child/grandchild",
393  "item/child/grandchild/grandgrandchild",
394  "root level"};
395  assert_expected_tree_items(&tree, expected_paths);
396  }
397 
398  {
400 
401  std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item/child");
402  tree.insert_item(*catalog);
403  assert_expected_tree_items(&tree, {"item", "item/child"});
404  }
405 
406  {
408 
409  std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("white space");
410  tree.insert_item(*catalog);
411  assert_expected_tree_items(&tree, {"white space"});
412  }
413 
414  {
416 
417  std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("/item/white space");
418  tree.insert_item(*catalog);
419  assert_expected_tree_items(&tree, {"item", "item/white space"});
420  }
421 
422  {
424 
425  std::unique_ptr<AssetCatalog> catalog_unicode_path = AssetCatalog::from_path("Ružena");
426  tree.insert_item(*catalog_unicode_path);
427  assert_expected_tree_items(&tree, {"Ružena"});
428 
429  catalog_unicode_path = AssetCatalog::from_path("Ružena/Ružena");
430  tree.insert_item(*catalog_unicode_path);
431  assert_expected_tree_items(&tree, {"Ružena", "Ružena/Ružena"});
432  }
433 }
434 
435 TEST_F(AssetCatalogTest, load_single_file_into_tree)
436 {
437  AssetCatalogService service(asset_library_root_);
438  service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
439 
440  /* Contains not only paths from the CDF but also the missing parents (implicitly defined
441  * catalogs). */
442  std::vector<AssetCatalogPath> expected_paths{
443  "character",
444  "character/Ellie",
445  "character/Ellie/backslashes",
446  "character/Ellie/poselib",
447  "character/Ellie/poselib/tailslash",
448  "character/Ellie/poselib/white space",
449  "character/Ružena",
450  "character/Ružena/poselib",
451  "character/Ružena/poselib/face",
452  "character/Ružena/poselib/hand",
453  "path", /* Implicit. */
454  "path/without", /* Implicit. */
455  "path/without/simplename", /* From CDF. */
456  };
457 
459  assert_expected_tree_items(tree, expected_paths);
460 }
461 
462 TEST_F(AssetCatalogTest, foreach_in_tree)
463 {
464  {
466  const std::vector<AssetCatalogPath> no_catalogs{};
467 
468  assert_expected_tree_items(&tree, no_catalogs);
469  assert_expected_tree_root_items(&tree, no_catalogs);
470  /* Need a root item to check child items. */
471  std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("something");
472  tree.insert_item(*catalog);
473  tree.foreach_root_item([&no_catalogs, this](AssetCatalogTreeItem &item) {
474  assert_expected_tree_item_child_items(&item, no_catalogs);
475  });
476  }
477 
478  AssetCatalogService service(asset_library_root_);
479  service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
480 
481  std::vector<AssetCatalogPath> expected_root_items{{"character", "path"}};
483  assert_expected_tree_root_items(tree, expected_root_items);
484 
485  /* Test if the direct children of the root item are what's expected. */
486  std::vector<std::vector<AssetCatalogPath>> expected_root_child_items = {
487  /* Children of the "character" root item. */
488  {"character/Ellie", "character/Ružena"},
489  /* Children of the "path" root item. */
490  {"path/without"},
491  };
492  int i = 0;
493  tree->foreach_root_item([&expected_root_child_items, &i, this](AssetCatalogTreeItem &item) {
494  assert_expected_tree_item_child_items(&item, expected_root_child_items[i]);
495  i++;
496  });
497 }
498 
499 TEST_F(AssetCatalogTest, find_catalog_by_path)
500 {
501  TestableAssetCatalogService service(asset_library_root_);
502  service.load_from_disk(asset_library_root_ + "/" +
504 
505  AssetCatalog *catalog;
506 
507  EXPECT_EQ(nullptr, service.find_catalog_by_path(""));
508  catalog = service.find_catalog_by_path("character/Ellie/poselib/white space");
509  EXPECT_NE(nullptr, catalog);
511  catalog = service.find_catalog_by_path("character/Ružena/poselib");
512  EXPECT_NE(nullptr, catalog);
514 
515  /* "character/Ellie/poselib" is used by two catalogs. Check if it's using the first one. */
516  catalog = service.find_catalog_by_path("character/Ellie/poselib");
517  EXPECT_NE(nullptr, catalog);
519  EXPECT_NE(UUID_POSES_ELLIE_TRAILING_SLASH, catalog->catalog_id);
520 }
521 
522 TEST_F(AssetCatalogTest, write_single_file)
523 {
524  TestableAssetCatalogService service(asset_library_root_);
525  service.load_from_disk(asset_library_root_ + "/" +
527 
528  const CatalogFilePath save_to_path = use_temp_path() +
531  cdf->write_to_disk(save_to_path);
532 
533  AssetCatalogService loaded_service(save_to_path);
534  loaded_service.load_from_disk();
535 
536  /* Test that the expected catalogs are there. */
537  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE));
538  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE));
539  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
540  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA));
541  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND));
542  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE));
543 
544  /* Test that the invalid catalog definition wasn't copied. */
545  EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_ID_WITHOUT_PATH));
546 
547  /* TODO(@sybren): test ordering of catalogs in the file. */
548 }
549 
550 TEST_F(AssetCatalogTest, read_write_unicode_filepath)
551 {
552  TestableAssetCatalogService service(asset_library_root_);
553  const CatalogFilePath load_from_path = asset_library_root_ + "/новый/" +
555  service.load_from_disk(load_from_path);
556 
557  const CatalogFilePath save_to_path = use_temp_path() + "новый.cats.txt";
559  ASSERT_NE(nullptr, cdf) << "unable to load " << load_from_path;
560  EXPECT_TRUE(cdf->write_to_disk(save_to_path));
561 
562  AssetCatalogService loaded_service(save_to_path);
563  loaded_service.load_from_disk();
564 
565  /* Test that the file was loaded correctly. */
566  const bUUID materials_uuid("a2151dff-dead-4f29-b6bc-b2c7d6cccdb4");
567  const AssetCatalog *cat = loaded_service.find_catalog(materials_uuid);
568  ASSERT_NE(nullptr, cat);
569  EXPECT_EQ(materials_uuid, cat->catalog_id);
570  EXPECT_EQ(AssetCatalogPath("Материалы"), cat->path);
571  EXPECT_EQ("Russian Materials", cat->simple_name);
572 }
573 
574 TEST_F(AssetCatalogTest, no_writing_empty_files)
575 {
576  const CatalogFilePath temp_lib_root = create_temp_path();
577  AssetCatalogService service(temp_lib_root);
578  service.write_to_disk(temp_lib_root + "phony.blend");
579 
580  const CatalogFilePath default_cdf_path = temp_lib_root +
582  EXPECT_FALSE(BLI_exists(default_cdf_path.c_str()));
583 }
584 
585 /* Already loaded a CDF, saving to some unrelated directory. */
586 TEST_F(AssetCatalogTest, on_blendfile_save__with_existing_cdf)
587 {
588  const CatalogFilePath top_level_dir = create_temp_path(); /* Has trailing slash. */
589 
590  /* Create a copy of the CDF in SVN, so we can safely write to it. */
591  const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
592  const CatalogFilePath cdf_dirname = top_level_dir + "other_dir/";
593  const CatalogFilePath cdf_filename = cdf_dirname + AssetCatalogService::DEFAULT_CATALOG_FILENAME;
594  ASSERT_TRUE(BLI_dir_create_recursive(cdf_dirname.c_str()));
595  ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), cdf_filename.c_str()))
596  << "Unable to copy " << original_cdf_file << " to " << cdf_filename;
597 
598  /* Load the CDF, add a catalog, and trigger a write. This should write to the loaded CDF. */
599  TestableAssetCatalogService service(cdf_filename);
600  service.load_from_disk();
601  const AssetCatalog *cat = service.create_catalog("some/catalog/path");
602 
603  const CatalogFilePath blendfilename = top_level_dir + "subdir/some_file.blend";
604  ASSERT_TRUE(service.write_to_disk(blendfilename));
605  EXPECT_EQ(cdf_filename, service.get_catalog_definition_file()->file_path);
606 
607  /* Test that the CDF was created in the expected location. */
608  const CatalogFilePath backup_filename = cdf_filename + "~";
609  EXPECT_TRUE(BLI_exists(cdf_filename.c_str()));
610  EXPECT_TRUE(BLI_exists(backup_filename.c_str()))
611  << "Overwritten CDF should have been backed up.";
612 
613  /* Test that the on-disk CDF contains the expected catalogs. */
614  AssetCatalogService loaded_service(cdf_filename);
615  loaded_service.load_from_disk();
616  EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id))
617  << "Expected to see the newly-created catalog.";
618  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE))
619  << "Expected to see the already-existing catalog.";
620 }
621 
622 /* Create some catalogs in memory, save to directory that doesn't contain anything else. */
623 TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_empty_directory)
624 {
625  const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */
626 
628  const AssetCatalog *cat = service.create_catalog("some/catalog/path");
629 
630  const CatalogFilePath blendfilename = target_dir + "some_file.blend";
631  ASSERT_TRUE(service.write_to_disk(blendfilename));
632 
633  /* Test that the CDF was created in the expected location. */
634  const CatalogFilePath expected_cdf_path = target_dir +
636  EXPECT_TRUE(BLI_exists(expected_cdf_path.c_str()));
637 
638  /* Test that the in-memory CDF has been created, and contains the expected catalog. */
640  ASSERT_NE(nullptr, cdf);
641  EXPECT_TRUE(cdf->contains(cat->catalog_id));
642 
643  /* Test that the on-disk CDF contains the expected catalog. */
644  AssetCatalogService loaded_service(expected_cdf_path);
645  loaded_service.load_from_disk();
646  EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id));
647 }
648 
649 /* Create some catalogs in memory, save to directory that contains a default CDF. */
650 TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_merge)
651 {
652  const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */
653  const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
654  CatalogFilePath writable_cdf_file = target_dir + AssetCatalogService::DEFAULT_CATALOG_FILENAME;
655  BLI_path_slash_native(writable_cdf_file.data());
656  ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str()));
657 
658  /* Create the catalog service without loading the already-existing CDF. */
660  const AssetCatalog *cat = service.create_catalog("some/catalog/path");
661 
662  /* Mock that the blend file is written to a subdirectory of the asset library. */
663  const CatalogFilePath blendfilename = target_dir + "some_file.blend";
664  ASSERT_TRUE(service.write_to_disk(blendfilename));
665 
666  /* Test that the CDF still exists in the expected location. */
667  const CatalogFilePath backup_filename = writable_cdf_file + "~";
668  EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str()));
669  EXPECT_TRUE(BLI_exists(backup_filename.c_str()))
670  << "Overwritten CDF should have been backed up.";
671 
672  /* Test that the in-memory CDF has the expected file path. */
674  ASSERT_NE(nullptr, cdf);
675  EXPECT_EQ(writable_cdf_file, cdf->file_path);
676 
677  /* Test that the in-memory catalogs have been merged with the on-disk one. */
678  AssetCatalogService loaded_service(writable_cdf_file);
679  loaded_service.load_from_disk();
680  EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id));
681  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE));
682 }
683 
684 /* Create some catalogs in memory, save to subdirectory of a registered asset library, where the
685  * subdirectory also contains a CDF. This should still write to the top-level dir of the asset
686  * library. */
688  on_blendfile_save__from_memory_into_existing_asset_lib_without_top_level_cdf)
689 {
690  save_from_memory_into_existing_asset_lib(true);
691 }
692 
693 /* Create some catalogs in memory, save to subdirectory of a registered asset library, where the
694  * subdirectory contains a CDF, but the top-level directory does not. This should still write to
695  * the top-level dir of the asset library. */
696 TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_asset_lib)
697 {
698  save_from_memory_into_existing_asset_lib(false);
699 }
700 
701 TEST_F(AssetCatalogTest, create_first_catalog_from_scratch)
702 {
703  /* Even from scratch a root directory should be known. */
704  const CatalogFilePath temp_lib_root = use_temp_path();
705  AssetCatalogService service;
706 
707  /* Just creating the service should NOT create the path. */
708  EXPECT_FALSE(BLI_exists(temp_lib_root.c_str()));
709 
710  AssetCatalog *cat = service.create_catalog("some/catalog/path");
711  ASSERT_NE(nullptr, cat);
712  EXPECT_EQ(cat->path, "some/catalog/path");
713  EXPECT_EQ(cat->simple_name, "some-catalog-path");
714 
715  /* Creating a new catalog should not save anything to disk yet. */
716  EXPECT_FALSE(BLI_exists(temp_lib_root.c_str()));
717 
718  /* Writing to disk should create the directory + the default file. */
719  service.write_to_disk(temp_lib_root + "phony.blend");
720  EXPECT_TRUE(BLI_is_dir(temp_lib_root.c_str()));
721 
722  const CatalogFilePath definition_file_path = temp_lib_root + "/" +
724  EXPECT_TRUE(BLI_is_file(definition_file_path.c_str()));
725 
726  AssetCatalogService loaded_service(temp_lib_root);
727  loaded_service.load_from_disk();
728 
729  /* Test that the expected catalog is there. */
730  AssetCatalog *written_cat = loaded_service.find_catalog(cat->catalog_id);
731  ASSERT_NE(nullptr, written_cat);
732  EXPECT_EQ(written_cat->catalog_id, cat->catalog_id);
733  EXPECT_EQ(written_cat->path, cat->path.str());
734 }
735 
736 TEST_F(AssetCatalogTest, create_catalog_after_loading_file)
737 {
738  const CatalogFilePath temp_lib_root = create_temp_path();
739 
740  /* Copy the asset catalog definition files to a separate location, so that we can test without
741  * overwriting the test file in SVN. */
742  const CatalogFilePath default_catalog_path = asset_library_root_ + "/" +
744  const CatalogFilePath writable_catalog_path = temp_lib_root +
746  ASSERT_EQ(0, BLI_copy(default_catalog_path.c_str(), writable_catalog_path.c_str()));
747  EXPECT_TRUE(BLI_is_dir(temp_lib_root.c_str()));
748  EXPECT_TRUE(BLI_is_file(writable_catalog_path.c_str()));
749 
750  TestableAssetCatalogService service(temp_lib_root);
751  service.load_from_disk();
752  EXPECT_EQ(writable_catalog_path, service.get_catalog_definition_file()->file_path);
753  EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_ELLIE)) << "expected catalogs to be loaded";
754 
755  /* This should create a new catalog but not write to disk. */
756  const AssetCatalog *new_catalog = service.create_catalog("new/catalog");
757  const bUUID new_catalog_id = new_catalog->catalog_id;
758 
759  /* Reload the on-disk catalog file. */
760  TestableAssetCatalogService loaded_service(temp_lib_root);
761  loaded_service.load_from_disk();
762  EXPECT_EQ(writable_catalog_path, loaded_service.get_catalog_definition_file()->file_path);
763 
764  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE))
765  << "expected pre-existing catalogs to be kept in the file";
766  EXPECT_EQ(nullptr, loaded_service.find_catalog(new_catalog_id))
767  << "expecting newly added catalog to not yet be saved to " << temp_lib_root;
768 
769  /* Write and reload the catalog file. */
770  service.write_to_disk(temp_lib_root + "phony.blend");
771  AssetCatalogService reloaded_service(temp_lib_root);
772  reloaded_service.load_from_disk();
773  EXPECT_NE(nullptr, reloaded_service.find_catalog(UUID_POSES_ELLIE))
774  << "expected pre-existing catalogs to be kept in the file";
775  EXPECT_NE(nullptr, reloaded_service.find_catalog(new_catalog_id))
776  << "expecting newly added catalog to exist in the file";
777 }
778 
779 TEST_F(AssetCatalogTest, create_catalog_path_cleanup)
780 {
781  AssetCatalogService service;
782  AssetCatalog *cat = service.create_catalog(" /some/path / ");
783 
784  EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id));
785  EXPECT_EQ("some/path", cat->path.str());
786  EXPECT_EQ("some-path", cat->simple_name);
787 }
788 
789 TEST_F(AssetCatalogTest, create_catalog_simple_name)
790 {
791  AssetCatalogService service;
792  AssetCatalog *cat = service.create_catalog(
793  "production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands");
794 
795  EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id));
796  EXPECT_EQ("production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands",
797  cat->path.str());
798  EXPECT_EQ("...ht-Characters-Victora-Pose Library-Approved-Body Parts-Hands", cat->simple_name);
799 }
800 
801 TEST_F(AssetCatalogTest, delete_catalog_leaf)
802 {
803  AssetCatalogService service(asset_library_root_);
804  service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
805 
806  /* Delete a leaf catalog, i.e. one that is not a parent of another catalog.
807  * This keeps this particular test easy. */
809  EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_HAND));
810 
811  /* Contains not only paths from the CDF but also the missing parents (implicitly defined
812  * catalogs). This is why a leaf catalog was deleted. */
813  std::vector<AssetCatalogPath> expected_paths{
814  "character",
815  "character/Ellie",
816  "character/Ellie/backslashes",
817  "character/Ellie/poselib",
818  "character/Ellie/poselib/tailslash",
819  "character/Ellie/poselib/white space",
820  "character/Ružena",
821  "character/Ružena/poselib",
822  "character/Ružena/poselib/face",
823  // "character/Ružena/poselib/hand", /* This is the deleted one. */
824  "path",
825  "path/without",
826  "path/without/simplename",
827  };
828 
830  assert_expected_tree_items(tree, expected_paths);
831 }
832 
833 TEST_F(AssetCatalogTest, delete_catalog_parent_by_id)
834 {
835  TestableAssetCatalogService service(asset_library_root_);
836  service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
837 
838  /* Delete a parent catalog. */
840 
841  /* The catalog should have been deleted, but its children should still be there. */
842  EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA));
843  EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_RUZENA_FACE));
844  EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_RUZENA_HAND));
845 }
846 
847 TEST_F(AssetCatalogTest, delete_catalog_parent_by_path)
848 {
849  AssetCatalogService service(asset_library_root_);
850  service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
851 
852  /* Create an extra catalog with the to-be-deleted path, and one with a child of that.
853  * This creates some duplicates that are bound to occur in production asset libraries as well.
854  */
855  const bUUID cat1_uuid = service.create_catalog("character/Ružena/poselib")->catalog_id;
856  const bUUID cat2_uuid = service.create_catalog("character/Ružena/poselib/body")->catalog_id;
857 
858  /* Delete a parent catalog. */
859  service.prune_catalogs_by_path("character/Ružena/poselib");
860 
861  /* The catalogs and their children should have been deleted. */
862  EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA));
863  EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_FACE));
864  EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_HAND));
865  EXPECT_EQ(nullptr, service.find_catalog(cat1_uuid));
866  EXPECT_EQ(nullptr, service.find_catalog(cat2_uuid));
867 
868  /* Contains not only paths from the CDF but also the missing parents (implicitly defined
869  * catalogs). This is why a leaf catalog was deleted. */
870  std::vector<AssetCatalogPath> expected_paths{
871  "character",
872  "character/Ellie",
873  "character/Ellie/backslashes",
874  "character/Ellie/poselib",
875  "character/Ellie/poselib/tailslash",
876  "character/Ellie/poselib/white space",
877  "character/Ružena",
878  "path",
879  "path/without",
880  "path/without/simplename",
881  };
882 
884  assert_expected_tree_items(tree, expected_paths);
885 }
886 
887 TEST_F(AssetCatalogTest, delete_catalog_write_to_disk)
888 {
889  TestableAssetCatalogService service(asset_library_root_);
890  service.load_from_disk(asset_library_root_ + "/" +
892 
894 
895  const CatalogFilePath save_to_path = use_temp_path();
898 
899  AssetCatalogService loaded_service(save_to_path);
900  loaded_service.load_from_disk();
901 
902  /* Test that the expected catalogs are there, except the deleted one. */
903  EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE));
904  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE));
905  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
906  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA));
907  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND));
908  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE));
909 }
910 
911 TEST_F(AssetCatalogTest, update_catalog_path)
912 {
913  AssetCatalogService service(asset_library_root_);
914  service.load_from_disk(asset_library_root_ + "/" +
916 
917  const AssetCatalog *orig_cat = service.find_catalog(UUID_POSES_RUZENA);
918  const AssetCatalogPath orig_path = orig_cat->path;
919 
920  service.update_catalog_path(UUID_POSES_RUZENA, "charlib/Ružena");
921 
922  EXPECT_EQ(nullptr, service.find_catalog_by_path(orig_path))
923  << "The original (pre-rename) path should not be associated with a catalog any more.";
924 
925  const AssetCatalog *renamed_cat = service.find_catalog(UUID_POSES_RUZENA);
926  ASSERT_NE(nullptr, renamed_cat);
927  ASSERT_EQ(orig_cat, renamed_cat) << "Changing the path should not reallocate the catalog.";
928  EXPECT_EQ(orig_cat->catalog_id, renamed_cat->catalog_id)
929  << "Changing the path should not change the catalog ID.";
930 
931  EXPECT_EQ("charlib/Ružena", renamed_cat->path.str())
932  << "Changing the path should change the path. Surprise.";
933 
934  EXPECT_EQ("charlib/Ružena/hand", service.find_catalog(UUID_POSES_RUZENA_HAND)->path.str())
935  << "Changing the path should update children.";
936  EXPECT_EQ("charlib/Ružena/face", service.find_catalog(UUID_POSES_RUZENA_FACE)->path.str())
937  << "Changing the path should update children.";
938 }
939 
940 TEST_F(AssetCatalogTest, update_catalog_path_simple_name)
941 {
942  AssetCatalogService service(asset_library_root_);
943  service.load_from_disk(asset_library_root_ + "/" +
945  service.update_catalog_path(UUID_POSES_RUZENA, "charlib/Ružena");
946 
947  /* This may not be valid forever; maybe at some point we'll expose the simple name to users &
948  * let them change it from the UI. Until then, automatically updating it is better, because
949  * otherwise all simple names would be "Catalog". */
950  EXPECT_EQ("charlib-Ružena", service.find_catalog(UUID_POSES_RUZENA)->simple_name)
951  << "Changing the path should update the simplename.";
952  EXPECT_EQ("charlib-Ružena-face", service.find_catalog(UUID_POSES_RUZENA_FACE)->simple_name)
953  << "Changing the path should update the simplename of children.";
954 }
955 
956 TEST_F(AssetCatalogTest, update_catalog_path_longer_than_simplename)
957 {
958  AssetCatalogService service(asset_library_root_);
959  service.load_from_disk(asset_library_root_ + "/" +
961  const std::string new_path =
962  "this/is/a/very/long/path/that/exceeds/the/simple-name/length/of/assets";
963  ASSERT_GT(new_path.length(), sizeof(AssetMetaData::catalog_simple_name))
964  << "This test case should work with paths longer than AssetMetaData::catalog_simple_name";
965 
966  service.update_catalog_path(UUID_POSES_RUZENA, new_path);
967 
968  const std::string new_simple_name = service.find_catalog(UUID_POSES_RUZENA)->simple_name;
969  EXPECT_LT(new_simple_name.length(), sizeof(AssetMetaData::catalog_simple_name))
970  << "The new simple name should fit in the asset metadata.";
971  EXPECT_EQ("...very-long-path-that-exceeds-the-simple-name-length-of-assets", new_simple_name)
972  << "Changing the path should update the simplename.";
973  EXPECT_EQ("...long-path-that-exceeds-the-simple-name-length-of-assets-face",
975  << "Changing the path should update the simplename of children.";
976 }
977 
978 TEST_F(AssetCatalogTest, update_catalog_path_add_slashes)
979 {
980  AssetCatalogService service(asset_library_root_);
981  service.load_from_disk(asset_library_root_ + "/" +
983 
984  const AssetCatalog *orig_cat = service.find_catalog(UUID_POSES_RUZENA);
985  const AssetCatalogPath orig_path = orig_cat->path;
986 
987  /* Original path is `character/Ružena/poselib`.
988  * This rename will also create a new catalog for `character/Ružena/poses`. */
989  service.update_catalog_path(UUID_POSES_RUZENA, "character/Ružena/poses/general");
990 
991  EXPECT_EQ(nullptr, service.find_catalog_by_path(orig_path))
992  << "The original (pre-rename) path should not be associated with a catalog any more.";
993 
994  const AssetCatalog *renamed_cat = service.find_catalog(UUID_POSES_RUZENA);
995  ASSERT_NE(nullptr, renamed_cat);
996  EXPECT_EQ(orig_cat->catalog_id, renamed_cat->catalog_id)
997  << "Changing the path should not change the catalog ID.";
998 
999  EXPECT_EQ("character/Ružena/poses/general", renamed_cat->path.str())
1000  << "When creating a new catalog by renaming + adding a slash, the renamed catalog should be "
1001  "assigned the path passed to update_catalog_path()";
1002 
1003  /* Test the newly created catalog. */
1004  const AssetCatalog *new_cat = service.find_catalog_by_path("character/Ružena/poses");
1005  ASSERT_NE(nullptr, new_cat) << "Renaming to .../X/Y should cause .../X to exist as well.";
1006  EXPECT_EQ("character/Ružena/poses", new_cat->path.str());
1007  EXPECT_EQ("character-Ružena-poses", new_cat->simple_name);
1008  EXPECT_TRUE(new_cat->flags.has_unsaved_changes);
1009 
1010  /* Test the children. */
1011  EXPECT_EQ("character/Ružena/poses/general/hand",
1013  << "Changing the path should update children.";
1014  EXPECT_EQ("character/Ružena/poses/general/face",
1016  << "Changing the path should update children.";
1017 }
1018 
1019 TEST_F(AssetCatalogTest, merge_catalog_files)
1020 {
1021  const CatalogFilePath cdf_dir = create_temp_path();
1022  const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
1023  const CatalogFilePath modified_cdf_file = asset_library_root_ + "/modified_assets.cats.txt";
1024  const CatalogFilePath temp_cdf_file = cdf_dir + "blender_assets.cats.txt";
1025  ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), temp_cdf_file.c_str()));
1026 
1027  /* Load the unmodified, original CDF. */
1028  TestableAssetCatalogService service(asset_library_root_);
1029  service.load_from_disk(cdf_dir);
1030 
1031  /* Copy a modified file, to mimic a situation where someone changed the
1032  * CDF after we loaded it. */
1033  ASSERT_EQ(0, BLI_copy(modified_cdf_file.c_str(), temp_cdf_file.c_str()));
1034 
1035  /* Overwrite the modified file. This should merge the on-disk file with our catalogs.
1036  * No catalog was marked as "has unsaved changes", so effectively this should not
1037  * save anything, and reload what's on disk. */
1038  service.write_to_disk(cdf_dir + "phony.blend");
1039 
1040  AssetCatalogService loaded_service(cdf_dir);
1041  loaded_service.load_from_disk();
1042 
1043  /* Test that the expected catalogs are there. */
1044  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE));
1045  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE));
1046  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_AGENT_47)); /* New in the modified file. */
1047 
1048  /* Test that catalogs removed from modified CDF are gone. */
1049  EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE));
1050  EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
1051  EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA));
1052  EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND));
1053 
1054  /* On-disk changed catalogs should have overridden in-memory not-changed ones. */
1055  const AssetCatalog *ruzena_face = loaded_service.find_catalog(UUID_POSES_RUZENA_FACE);
1056  EXPECT_EQ("character/Ružena/poselib/gezicht", ruzena_face->path.str());
1057 }
1058 
1059 TEST_F(AssetCatalogTest, refresh_catalogs_with_modification)
1060 {
1061  const CatalogFilePath cdf_dir = create_temp_path();
1062  const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
1063  const CatalogFilePath modified_cdf_file = asset_library_root_ + "/catalog_reload_test.cats.txt";
1064  const CatalogFilePath temp_cdf_file = cdf_dir + "blender_assets.cats.txt";
1065  ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), temp_cdf_file.c_str()));
1066 
1067  /* Load the unmodified, original CDF. */
1068  TestableAssetCatalogService service(asset_library_root_);
1069  service.load_from_disk(cdf_dir);
1070 
1071  /* === Perform changes that should be handled gracefully by the reloading code: */
1072 
1073  /* 1. Delete a subtree of catalogs. */
1075  /* 2. Rename a catalog. */
1077  service.update_catalog_path(UUID_POSES_ELLIE_TRAILING_SLASH, "character/Ellie/test-value");
1078 
1079  /* Copy a modified file, to mimic a situation where someone changed the
1080  * CDF after we loaded it. */
1081  ASSERT_EQ(0, BLI_copy(modified_cdf_file.c_str(), temp_cdf_file.c_str()));
1082 
1083  AssetCatalog *const ellie_whitespace_before_reload = service.find_catalog(
1085 
1086  /* This should merge the on-disk file with our catalogs. */
1087  service.reload_catalogs();
1088 
1089  /* === Test that the expected catalogs are there. */
1090  EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_ELLIE));
1091  EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_ELLIE_WHITESPACE));
1092  EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
1093 
1094  /* === Test changes made to the CDF: */
1095 
1096  /* Removed from the file. */
1098  /* Added to the file. */
1099  EXPECT_NE(nullptr, service.find_catalog(UUID_AGENT_47));
1100  /* Path modified in file. */
1101  AssetCatalog *ellie_whitespace_after_reload = service.find_catalog(UUID_POSES_ELLIE_WHITESPACE);
1102  EXPECT_EQ(AssetCatalogPath("whitespace from file"), ellie_whitespace_after_reload->path);
1103  EXPECT_NE(ellie_whitespace_after_reload, ellie_whitespace_before_reload);
1104  /* Simple name modified in file. */
1105  EXPECT_EQ(std::string("Hah simple name after all"),
1107 
1108  /* === Test persistence of in-memory changes: */
1109 
1110  /* This part of the tree we deleted, but still existed in the CDF. They should remain deleted
1111  * after reloading: */
1112  EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA));
1113  EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_HAND));
1114  EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_FACE));
1115 
1116  /* This catalog had its path changed in the test and in the CDF. The change from the test (i.e.
1117  * the in-memory, yet-unsaved change) should persist. */
1118  EXPECT_EQ(AssetCatalogPath("character/Ellie/test-value"),
1120 
1121  /* Overwrite the modified file. This should merge the on-disk file with our catalogs, and clear
1122  * the "has_unsaved_changes" flags. */
1123  service.write_to_disk(cdf_dir + "phony.blend");
1124 
1126  << "The catalogs whose path we changed should now be saved";
1127  EXPECT_TRUE(service.get_deleted_catalogs().is_empty())
1128  << "Deleted catalogs should not be remembered after saving.";
1129 }
1130 
1132 {
1133  const CatalogFilePath cdf_dir = create_temp_path();
1134  const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
1135  const CatalogFilePath writable_cdf_file = cdf_dir + "/blender_assets.cats.txt";
1136  ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str()));
1137 
1138  /* Read a CDF, modify, and write it. */
1139  TestableAssetCatalogService service(cdf_dir);
1140  service.load_from_disk();
1142  service.write_to_disk(cdf_dir + "phony.blend");
1143 
1144  const CatalogFilePath backup_path = writable_cdf_file + "~";
1145  ASSERT_TRUE(BLI_is_file(backup_path.c_str()));
1146 
1147  AssetCatalogService loaded_service;
1148  loaded_service.load_from_disk(backup_path);
1149 
1150  /* Test that the expected catalogs are there, including the deleted one.
1151  * This is the backup, after all. */
1152  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE));
1153  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE));
1154  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
1155  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA));
1156  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND));
1157  EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE));
1158 }
1159 
1160 TEST_F(AssetCatalogTest, order_by_path)
1161 {
1162  const bUUID cat2_uuid("22222222-b847-44d9-bdca-ff04db1c24f5");
1163  const bUUID cat4_uuid("11111111-b847-44d9-bdca-ff04db1c24f5"); /* Sorts earlier than above. */
1164  const AssetCatalog cat1(BLI_uuid_generate_random(), "simple/path/child", "");
1165  const AssetCatalog cat2(cat2_uuid, "simple/path", "");
1166  const AssetCatalog cat3(BLI_uuid_generate_random(), "complex/path/...or/is/it?", "");
1167  const AssetCatalog cat4(
1168  cat4_uuid, "simple/path", "different ID, same path"); /* should be kept */
1169  const AssetCatalog cat5(cat4_uuid, "simple/path", "same ID, same path"); /* disappears */
1170 
1171  AssetCatalogOrderedSet by_path;
1172  by_path.insert(&cat1);
1173  by_path.insert(&cat2);
1174  by_path.insert(&cat3);
1175  by_path.insert(&cat4);
1176  by_path.insert(&cat5);
1177 
1178  AssetCatalogOrderedSet::const_iterator set_iter = by_path.begin();
1179 
1180  EXPECT_EQ(1, by_path.count(&cat1));
1181  EXPECT_EQ(1, by_path.count(&cat2));
1182  EXPECT_EQ(1, by_path.count(&cat3));
1183  EXPECT_EQ(1, by_path.count(&cat4));
1184  ASSERT_EQ(4, by_path.size()) << "Expecting cat5 to not be stored in the set, as it duplicates "
1185  "an already-existing path + UUID";
1186 
1187  EXPECT_EQ(cat3.catalog_id, (*(set_iter++))->catalog_id); /* complex/path */
1188  EXPECT_EQ(cat4.catalog_id, (*(set_iter++))->catalog_id); /* simple/path with 111.. ID */
1189  EXPECT_EQ(cat2.catalog_id, (*(set_iter++))->catalog_id); /* simple/path with 222.. ID */
1190  EXPECT_EQ(cat1.catalog_id, (*(set_iter++))->catalog_id); /* simple/path/child */
1191 
1192  if (set_iter != by_path.end()) {
1193  const AssetCatalog *next_cat = *set_iter;
1194  FAIL() << "Did not expect more items in the set, had at least " << next_cat->catalog_id << ":"
1195  << next_cat->path;
1196  }
1197 }
1198 
1199 TEST_F(AssetCatalogTest, order_by_path_and_first_seen)
1200 {
1201  AssetCatalogService service;
1202  service.load_from_disk(asset_library_root_);
1203 
1204  const bUUID first_seen_uuid("3d451c87-27d1-40fd-87fc-f4c9e829c848");
1205  const bUUID first_sorted_uuid("00000000-0000-0000-0000-000000000001");
1206  const bUUID last_sorted_uuid("ffffffff-ffff-ffff-ffff-ffffffffffff");
1207 
1208  AssetCatalog first_seen_cat(first_seen_uuid, "simple/path/child", "");
1209  const AssetCatalog first_sorted_cat(first_sorted_uuid, "simple/path/child", "");
1210  const AssetCatalog last_sorted_cat(last_sorted_uuid, "simple/path/child", "");
1211 
1212  /* Mimic that this catalog was first-seen when loading from disk. */
1213  first_seen_cat.flags.is_first_loaded = true;
1214 
1215  /* Just an assertion of the defaults; this is more to avoid confusing errors later on than an
1216  * actual test of these defaults. */
1217  ASSERT_FALSE(first_sorted_cat.flags.is_first_loaded);
1218  ASSERT_FALSE(last_sorted_cat.flags.is_first_loaded);
1219 
1220  AssetCatalogOrderedSet by_path;
1221  by_path.insert(&first_seen_cat);
1222  by_path.insert(&first_sorted_cat);
1223  by_path.insert(&last_sorted_cat);
1224 
1225  AssetCatalogOrderedSet::const_iterator set_iter = by_path.begin();
1226 
1227  EXPECT_EQ(1, by_path.count(&first_seen_cat));
1228  EXPECT_EQ(1, by_path.count(&first_sorted_cat));
1229  EXPECT_EQ(1, by_path.count(&last_sorted_cat));
1230  ASSERT_EQ(3, by_path.size());
1231 
1232  EXPECT_EQ(first_seen_uuid, (*(set_iter++))->catalog_id);
1233  EXPECT_EQ(first_sorted_uuid, (*(set_iter++))->catalog_id);
1234  EXPECT_EQ(last_sorted_uuid, (*(set_iter++))->catalog_id);
1235 
1236  if (set_iter != by_path.end()) {
1237  const AssetCatalog *next_cat = *set_iter;
1238  FAIL() << "Did not expect more items in the set, had at least " << next_cat->catalog_id << ":"
1239  << next_cat->path;
1240  }
1241 }
1242 
1243 TEST_F(AssetCatalogTest, create_missing_catalogs)
1244 {
1245  TestableAssetCatalogService new_service;
1246  new_service.create_catalog("path/with/missing/parents");
1247 
1248  EXPECT_EQ(nullptr, new_service.find_catalog_by_path("path/with/missing"))
1249  << "Missing parents should not be immediately created.";
1250  EXPECT_EQ(nullptr, new_service.find_catalog_by_path("")) << "Empty path should never be valid";
1251 
1252  new_service.create_missing_catalogs();
1253 
1254  EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with/missing"));
1255  EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with"));
1256  EXPECT_NE(nullptr, new_service.find_catalog_by_path("path"));
1257  EXPECT_EQ(nullptr, new_service.find_catalog_by_path(""))
1258  << "Empty path should never be valid, even when after missing catalogs";
1259 }
1260 
1261 TEST_F(AssetCatalogTest, create_missing_catalogs_after_loading)
1262 {
1263  TestableAssetCatalogService loaded_service(asset_library_root_);
1264  loaded_service.load_from_disk();
1265 
1266  const AssetCatalog *cat_char = loaded_service.find_catalog_by_path("character");
1267  const AssetCatalog *cat_ellie = loaded_service.find_catalog_by_path("character/Ellie");
1268  const AssetCatalog *cat_ruzena = loaded_service.find_catalog_by_path("character/Ružena");
1269  ASSERT_NE(nullptr, cat_char) << "Missing parents should be created immediately after loading.";
1270  ASSERT_NE(nullptr, cat_ellie) << "Missing parents should be created immediately after loading.";
1271  ASSERT_NE(nullptr, cat_ruzena) << "Missing parents should be created immediately after loading.";
1272 
1273  EXPECT_TRUE(cat_char->flags.has_unsaved_changes)
1274  << "Missing parents should be marked as having changes.";
1275  EXPECT_TRUE(cat_ellie->flags.has_unsaved_changes)
1276  << "Missing parents should be marked as having changes.";
1277  EXPECT_TRUE(cat_ruzena->flags.has_unsaved_changes)
1278  << "Missing parents should be marked as having changes.";
1279 
1281  ASSERT_NE(nullptr, cdf);
1282  EXPECT_TRUE(cdf->contains(cat_char->catalog_id)) << "Missing parents should be saved to a CDF.";
1283  EXPECT_TRUE(cdf->contains(cat_ellie->catalog_id)) << "Missing parents should be saved to a CDF.";
1284  EXPECT_TRUE(cdf->contains(cat_ruzena->catalog_id))
1285  << "Missing parents should be saved to a CDF.";
1286 
1287  /* Check that each missing parent is only created once. The CDF contains multiple paths that
1288  * could trigger the creation of missing parents, so this test makes sense. */
1289  EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character"));
1290  EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ellie"));
1291  EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ružena"));
1292 }
1293 
1294 TEST_F(AssetCatalogTest, create_catalog_filter)
1295 {
1296  AssetCatalogService service(asset_library_root_);
1297  service.load_from_disk();
1298 
1299  /* Alias for the same catalog as the main one. */
1300  AssetCatalog *alias_ruzena = service.create_catalog("character/Ružena/poselib");
1301  /* Alias for a sub-catalog. */
1302  AssetCatalog *alias_ruzena_hand = service.create_catalog("character/Ružena/poselib/hand");
1303 
1305 
1306  /* Positive test for loaded-from-disk catalogs. */
1307  EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA))
1308  << "Main catalog should be included in the filter.";
1309  EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_HAND))
1310  << "Sub-catalog should be included in the filter.";
1311  EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_FACE))
1312  << "Sub-catalog should be included in the filter.";
1313 
1314  /* Positive test for newly-created catalogs. */
1315  EXPECT_TRUE(filter.contains(alias_ruzena->catalog_id))
1316  << "Alias of main catalog should be included in the filter.";
1317  EXPECT_TRUE(filter.contains(alias_ruzena_hand->catalog_id))
1318  << "Alias of sub-catalog should be included in the filter.";
1319 
1320  /* Negative test for unrelated catalogs. */
1321  EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included.";
1322  EXPECT_FALSE(filter.contains(UUID_ID_WITHOUT_PATH));
1323  EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
1324  EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_WHITESPACE));
1325  EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_TRAILING_SLASH));
1326  EXPECT_FALSE(filter.contains(UUID_WITHOUT_SIMPLENAME));
1327 }
1328 
1329 TEST_F(AssetCatalogTest, create_catalog_filter_for_unknown_uuid)
1330 {
1331  AssetCatalogService service;
1332  const bUUID unknown_uuid = BLI_uuid_generate_random();
1333 
1334  AssetCatalogFilter filter = service.create_catalog_filter(unknown_uuid);
1335  EXPECT_TRUE(filter.contains(unknown_uuid));
1336 
1337  EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included.";
1338  EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
1339 }
1340 
1341 TEST_F(AssetCatalogTest, create_catalog_filter_for_unassigned_assets)
1342 {
1343  AssetCatalogService service;
1344 
1346  EXPECT_TRUE(filter.contains(BLI_uuid_nil()));
1347  EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
1348 }
1349 
1350 TEST_F(AssetCatalogTest, cat_collection_deep_copy__empty)
1351 {
1352  const AssetCatalogCollection empty;
1353  auto copy = empty.deep_copy();
1354  EXPECT_NE(&empty, copy.get());
1355 }
1356 
1358  public:
1360  {
1361  return catalogs_;
1362  }
1364  {
1365  return deleted_catalogs_;
1366  }
1368  {
1369  return catalog_definition_file_.get();
1370  }
1372  {
1373  catalog_definition_file_ = std::make_unique<AssetCatalogDefinitionFile>();
1374  return get_catalog_definition_file();
1375  }
1376 };
1377 
1378 TEST_F(AssetCatalogTest, cat_collection_deep_copy__nonempty_nocdf)
1379 {
1381  auto cat1 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA, "poses/Henrik", "");
1382  auto cat2 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_FACE, "poses/Henrik/face", "");
1383  auto cat3 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_HAND, "poses/Henrik/hands", "");
1384  cat3->flags.is_deleted = true;
1385 
1386  AssetCatalog *cat1_ptr = cat1.get();
1387  AssetCatalog *cat3_ptr = cat3.get();
1388 
1389  catcoll.get_catalogs().add_new(cat1->catalog_id, std::move(cat1));
1390  catcoll.get_catalogs().add_new(cat2->catalog_id, std::move(cat2));
1391  catcoll.get_deleted_catalogs().add_new(cat3->catalog_id, std::move(cat3));
1392 
1393  auto copy = catcoll.deep_copy();
1394  EXPECT_NE(&catcoll, copy.get());
1395 
1396  TestableAssetCatalogCollection *testcopy = reinterpret_cast<TestableAssetCatalogCollection *>(
1397  copy.get());
1398 
1399  /* Test catalogs & deleted catalogs. */
1400  EXPECT_EQ(2, testcopy->get_catalogs().size());
1401  EXPECT_EQ(1, testcopy->get_deleted_catalogs().size());
1402 
1403  ASSERT_TRUE(testcopy->get_catalogs().contains(UUID_POSES_RUZENA));
1404  ASSERT_TRUE(testcopy->get_catalogs().contains(UUID_POSES_RUZENA_FACE));
1405  ASSERT_TRUE(testcopy->get_deleted_catalogs().contains(UUID_POSES_RUZENA_HAND));
1406 
1407  EXPECT_NE(nullptr, testcopy->get_catalogs().lookup(UUID_POSES_RUZENA));
1408  EXPECT_NE(cat1_ptr, testcopy->get_catalogs().lookup(UUID_POSES_RUZENA).get())
1409  << "AssetCatalogs should be actual copies.";
1410 
1411  EXPECT_NE(nullptr, testcopy->get_deleted_catalogs().lookup(UUID_POSES_RUZENA_HAND));
1412  EXPECT_NE(cat3_ptr, testcopy->get_deleted_catalogs().lookup(UUID_POSES_RUZENA_HAND).get())
1413  << "AssetCatalogs should be actual copies.";
1414 }
1415 
1417  public:
1419  {
1420  return catalogs_;
1421  }
1422 };
1423 
1424 TEST_F(AssetCatalogTest, cat_collection_deep_copy__nonempty_cdf)
1425 {
1427  auto cat1 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA, "poses/Henrik", "");
1428  auto cat2 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_FACE, "poses/Henrik/face", "");
1429  auto cat3 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_HAND, "poses/Henrik/hands", "");
1430  cat3->flags.is_deleted = true;
1431 
1432  AssetCatalog *cat1_ptr = cat1.get();
1433  AssetCatalog *cat2_ptr = cat2.get();
1434  AssetCatalog *cat3_ptr = cat3.get();
1435 
1436  catcoll.get_catalogs().add_new(cat1->catalog_id, std::move(cat1));
1437  catcoll.get_catalogs().add_new(cat2->catalog_id, std::move(cat2));
1438  catcoll.get_deleted_catalogs().add_new(cat3->catalog_id, std::move(cat3));
1439 
1441  cdf->file_path = "path/to/somewhere.cats.txt";
1442  cdf->add_new(cat1_ptr);
1443  cdf->add_new(cat2_ptr);
1444  cdf->add_new(cat3_ptr);
1445 
1446  /* Test CDF remapping. */
1447  auto copy = catcoll.deep_copy();
1448  TestableAssetCatalogCollection *testable_copy = static_cast<TestableAssetCatalogCollection *>(
1449  copy.get());
1450 
1452  testable_copy->get_catalog_definition_file());
1453  EXPECT_EQ(testable_copy->get_catalogs().lookup(UUID_POSES_RUZENA).get(),
1454  cdf_copy->get_catalogs().lookup(UUID_POSES_RUZENA))
1455  << "AssetCatalog pointers should have been remapped to the copy.";
1456 
1458  cdf_copy->get_catalogs().lookup(UUID_POSES_RUZENA_HAND))
1459  << "Deleted AssetCatalog pointers should have been remapped to the copy.";
1460 }
1461 
1462 TEST_F(AssetCatalogTest, undo_redo_one_step)
1463 {
1464  TestableAssetCatalogService service(asset_library_root_);
1465  service.load_from_disk();
1466 
1467  EXPECT_FALSE(service.is_undo_possbile());
1468  EXPECT_FALSE(service.is_redo_possbile());
1469 
1470  service.create_catalog("some/catalog/path");
1471  EXPECT_FALSE(service.is_undo_possbile())
1472  << "Undo steps should be created explicitly, and not after creating any catalog.";
1473 
1474  service.undo_push();
1475  const bUUID other_catalog_id = service.create_catalog("other/catalog/path")->catalog_id;
1476  EXPECT_TRUE(service.is_undo_possbile())
1477  << "Undo should be possible after creating an undo snapshot.";
1478 
1479  /* Undo the creation of the catalog. */
1480  service.undo();
1481  EXPECT_FALSE(service.is_undo_possbile())
1482  << "Undoing the only stored step should make it impossible to undo further.";
1483  EXPECT_TRUE(service.is_redo_possbile()) << "Undoing a step should make redo possible.";
1484  EXPECT_EQ(nullptr, service.find_catalog_by_path("other/catalog/path"))
1485  << "Undone catalog should not exist after undo.";
1486  EXPECT_NE(nullptr, service.find_catalog_by_path("some/catalog/path"))
1487  << "First catalog should still exist after undo.";
1488  EXPECT_FALSE(service.get_catalog_definition_file()->contains(other_catalog_id))
1489  << "The CDF should also not contain the undone catalog.";
1490 
1491  /* Redo the creation of the catalog. */
1492  service.redo();
1493  EXPECT_TRUE(service.is_undo_possbile())
1494  << "Undoing and then redoing a step should make it possible to undo again.";
1495  EXPECT_FALSE(service.is_redo_possbile())
1496  << "Undoing and then redoing a step should make redo impossible.";
1497  EXPECT_NE(nullptr, service.find_catalog_by_path("other/catalog/path"))
1498  << "Redone catalog should exist after redo.";
1499  EXPECT_NE(nullptr, service.find_catalog_by_path("some/catalog/path"))
1500  << "First catalog should still exist after redo.";
1501  EXPECT_TRUE(service.get_catalog_definition_file()->contains(other_catalog_id))
1502  << "The CDF should contain the redone catalog.";
1503 }
1504 
1505 TEST_F(AssetCatalogTest, undo_redo_more_complex)
1506 {
1507  TestableAssetCatalogService service(asset_library_root_);
1508  service.load_from_disk();
1509 
1510  service.undo_push();
1511  service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)->simple_name = "Edited simple name";
1512 
1513  service.undo_push();
1514  service.find_catalog(UUID_POSES_ELLIE)->path = "poselib/EllieWithEditedPath";
1515 
1516  service.undo();
1517  service.undo();
1518 
1519  service.undo_push();
1520  service.find_catalog(UUID_POSES_ELLIE)->simple_name = "Ellie Simple";
1521 
1522  EXPECT_FALSE(service.is_redo_possbile())
1523  << "After storing an undo snapshot, the redo buffer should be empty.";
1524  EXPECT_TRUE(service.is_undo_possbile())
1525  << "After storing an undo snapshot, undoing should be possible";
1526 
1527  EXPECT_EQ(service.find_catalog(UUID_POSES_ELLIE)->simple_name, "Ellie Simple"); /* Not undone. */
1529  "POSES_ELLIE WHITESPACE"); /* Undone. */
1530  EXPECT_EQ(service.find_catalog(UUID_POSES_ELLIE)->path, "character/Ellie/poselib"); /* Undone. */
1531 }
1532 
1533 } // namespace blender::bke::tests
void BKE_tempdir_init(const char *userdir)
Definition: appdir.c:1133
struct bUserAssetLibrary * BKE_preferences_asset_library_add(struct UserDef *userdef, const char *name, const char *path) ATTR_NONNULL(1)
Definition: preferences.c:33
void BKE_preferences_asset_library_remove(struct UserDef *userdef, struct bUserAssetLibrary *library) ATTR_NONNULL()
Definition: preferences.c:51
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
File and directory operations.
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: storage.c:314
bool BLI_file_touch(const char *file) ATTR_NONNULL()
Definition: fileops.c:192
bool BLI_is_file(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: storage.c:402
int BLI_delete(const char *file, bool dir, bool recursive) ATTR_NONNULL()
Definition: fileops.c:934
size_t BLI_file_size(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: storage.c:187
bool BLI_is_dir(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: storage.c:397
bool BLI_dir_create_recursive(const char *dir) ATTR_NONNULL()
Definition: fileops.c:1219
int BLI_copy(const char *file, const char *to) ATTR_NONNULL()
Definition: fileops.c:1198
void BLI_path_slash_native(char *path) ATTR_NONNULL()
Definition: path_util.c:1805
bUUID BLI_uuid_nil(void)
Definition: uuid.cc:70
bool BLI_uuid_is_nil(bUUID uuid)
Definition: uuid.cc:76
bUUID BLI_uuid_generate_random(void)
Definition: uuid.cc:21
void CLG_exit(void)
Definition: clog.c:703
void CLG_init(void)
Definition: clog.c:696
unsigned int U
Definition: btGjkEpa3.h:78
const Value & lookup(const Key &key) const
Definition: BLI_map.hh:485
void add_new(const Key &key, const Value &value)
Definition: BLI_map.hh:220
int64_t size() const
Definition: BLI_map.hh:901
bool is_empty() const
Definition: BLI_map.hh:911
bool contains(const Key &key) const
Definition: BLI_map.hh:308
std::unique_ptr< AssetCatalogCollection > deep_copy() const
std::unique_ptr< AssetCatalogDefinitionFile > catalog_definition_file_
Map< CatalogID, AssetCatalog * > catalogs_
const std::string & str() const
AssetCatalog * create_catalog(const AssetCatalogPath &catalog_path)
bool write_to_disk(const CatalogFilePath &blend_file_path)
AssetCatalog * find_catalog_by_path(const AssetCatalogPath &path) const
void update_catalog_path(CatalogID catalog_id, const AssetCatalogPath &new_catalog_path)
void tag_has_unsaved_changes(AssetCatalog *edited_catalog)
AssetCatalogDefinitionFile * get_catalog_definition_file()
AssetCatalogFilter create_catalog_filter(CatalogID active_catalog_id) const
AssetCatalog * find_catalog(CatalogID catalog_id) const
void prune_catalogs_by_path(const AssetCatalogPath &path)
static const CatalogFilePath DEFAULT_CATALOG_FILENAME
struct blender::bke::AssetCatalog::Flags flags
static std::unique_ptr< AssetCatalog > from_path(const AssetCatalogPath &path)
void assert_expected_tree_items(AssetCatalogTree *tree, const std::vector< AssetCatalogPath > &expected_paths)
void assert_expected_item(const AssetCatalogPath &expected_path, const AssetCatalogTreeItem &actual_item)
void assert_expected_tree_item_child_items(AssetCatalogTreeItem *parent_item, const std::vector< AssetCatalogPath > &expected_paths)
void save_from_memory_into_existing_asset_lib(const bool should_top_level_cdf_exist)
void assert_expected_tree_root_items(AssetCatalogTree *tree, const std::vector< AssetCatalogPath > &expected_paths)
AssetCatalogDefinitionFile * allocate_catalog_definition_file()
AssetCatalogDefinitionFile * get_catalog_definition_file()
TestableAssetCatalogService(const CatalogFilePath &asset_library_root)
int64_t count_catalogs_with_path(const CatalogFilePath &path)
void * tree
DO_INLINE void filter(lfVector *V, fmatrix3x3 *S)
int count
void * BKE_tempdir_session
const bUUID UUID_ID_WITHOUT_PATH("e34dd2c5-5d2e-4668-9794-1db5de2a4f71")
const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78")
TEST_F(BKE_armature_find_selected_bones_test, some_bones_selected)
const bUUID UUID_WITHOUT_SIMPLENAME("d7916a31-6ca9-4909-955f-182ca2b81fa3")
const bUUID UUID_ANOTHER_RUZENA("00000000-d9fa-4b91-b704-e6af1f1339ef")
const bUUID UUID_POSES_RUZENA_HAND("81811c31-1a88-4bd7-bb34-c6fc2607a12e")
const bUUID UUID_POSES_ELLIE_BACKSLASHES("a51e17ae-34fc-47d5-ba0f-64c2c9b771f7")
const bUUID UUID_AGENT_47("c5744ba5-43f5-4f73-8e52-010ad4a61b34")
const bUUID UUID_POSES_ELLIE_WHITESPACE("b06132f6-5687-4751-a6dd-392740eb3c46")
const bUUID UUID_POSES_RUZENA_FACE("82162c1f-06cc-4d91-a9bf-4f72c104e348")
const bUUID UUID_POSES_ELLIE_TRAILING_SLASH("3376b94b-a28d-4d05-86c1-bf30b937130d")
const bUUID UUID_POSES_RUZENA("79a4f887-ab60-4bd4-94da-d572e27d6aed")
std::string CatalogFilePath
std::set< const AssetCatalog *, AssetCatalogLessThan > AssetCatalogOrderedSet
static void copy(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node)
__int64 int64_t
Definition: stdint.h:89
char catalog_simple_name[64]
Universally Unique Identifier according to RFC4122.