Blender V4.5
asset_library_service.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_blender.hh"
10#include "BKE_preferences.h"
11
12#include "BLI_fileops.h" // IWYU pragma: keep
13#include "BLI_path_utils.hh"
14#include "BLI_string_ref.hh"
15
16#include "DNA_asset_types.h"
17#include "DNA_userdef_types.h"
18
19#include "CLG_log.h"
20
21#include "AS_asset_library.hh"
23#include "all_library.hh"
25#include "asset_catalog_definition_file.hh" // IWYU pragma: keep
27#include "essentials_library.hh"
28#include "on_disk_library.hh"
30#include "runtime_library.hh"
31#include "utils.hh"
32
33/* When enabled, use a pre file load handler (#BKE_CB_EVT_LOAD_PRE) callback to destroy the asset
34 * library service. Without this an explicit call from the file loading code is needed to do this,
35 * which is not as nice.
36 *
37 * TODO Currently disabled because UI data depends on asset library data, so we have to make sure
38 * it's freed in the right order (UI first). Pre-load handlers don't give us this order.
39 * Should be addressed with a proper ownership model for the asset system:
40 * https://developer.blender.org/docs/features/asset_system/backend/#ownership-model
41 */
42// #define WITH_DESTROY_VIA_LOAD_HANDLER
43
44static CLG_LogRef LOG = {"asset_system.asset_library_service"};
45
46namespace blender::asset_system {
47
48std::unique_ptr<AssetLibraryService> AssetLibraryService::instance_;
49bool AssetLibraryService::atexit_handler_registered_ = false;
50
52{
53 if (!instance_) {
55 }
56 return instance_.get();
57}
58
60{
61 if (!instance_) {
62 return;
63 }
64 instance_->app_handler_unregister();
65 instance_.reset();
66}
67
69 const Main *bmain, const AssetLibraryReference &library_reference)
70{
71 const eAssetLibraryType type = eAssetLibraryType(library_reference.type);
72
73 switch (type) {
75 const StringRefNull root_path = essentials_directory_path();
76 if (root_path.is_empty()) {
77 return nullptr;
78 }
79
80 return this->get_asset_library_on_disk_builtin(type, root_path);
81 }
83 /* For the "Current File" library we get the asset library root path based on main. */
84 std::string root_path = bmain ? AS_asset_library_find_suitable_root_path_from_main(bmain) :
85 "";
86
87 if (root_path.empty()) {
88 /* File wasn't saved yet. */
89 return this->get_asset_library_current_file();
90 }
91 return this->get_asset_library_on_disk_builtin(type, root_path);
92 }
94 return this->get_asset_library_all(bmain);
97 library_reference);
98 if (!custom_library) {
99 return nullptr;
100 }
101
102 std::string root_path = custom_library->dirpath;
103 if (root_path.empty()) {
104 return nullptr;
105 }
106
107 AssetLibrary *library = this->get_asset_library_on_disk_custom_preferences(custom_library);
108 library->import_method_ = eAssetImportMethod(custom_library->import_method);
109 library->may_override_import_method_ = true;
110 library->use_relative_path_ = (custom_library->flag & ASSET_LIBRARY_RELATIVE_PATH) != 0;
111
112 return library;
113 }
114 }
115
116 return nullptr;
117}
118
120 eAssetLibraryType library_type,
121 StringRef name,
122 StringRefNull root_path,
123 const bool load_catalogs,
124 bUserAssetLibrary *preferences_library)
125{
126 const std::string normalized_root_path = utils::normalize_directory_path(root_path);
127
128 if (OnDiskAssetLibrary *lib = this->lookup_on_disk_library(library_type, normalized_root_path)) {
129 CLOG_INFO(&LOG, 2, "get \"%s\" (cached)", normalized_root_path.c_str());
130 if (load_catalogs) {
131 lib->load_or_reload_catalogs();
132 }
133 return lib;
134 }
135
136 std::unique_ptr<OnDiskAssetLibrary> lib_uptr;
137 switch (library_type) {
139 if (preferences_library) {
140 lib_uptr = std::make_unique<PreferencesOnDiskAssetLibrary>(name, normalized_root_path);
141 }
142 else {
143 lib_uptr = std::make_unique<OnDiskAssetLibrary>(library_type, name, normalized_root_path);
144 }
145 break;
147 lib_uptr = std::make_unique<EssentialsAssetLibrary>();
148 break;
149 default:
150 lib_uptr = std::make_unique<OnDiskAssetLibrary>(library_type, name, normalized_root_path);
151 break;
152 }
153
154 /* Get underlying pointer before moving. */
155 AssetLibrary *lib = lib_uptr.get();
156 on_disk_libraries_.add_new({library_type, normalized_root_path}, std::move(lib_uptr));
157 CLOG_INFO(&LOG, 2, "get \"%s\" (loaded)", normalized_root_path.c_str());
158
159 if (load_catalogs) {
160 lib->load_or_reload_catalogs();
161 }
162
163 return lib;
164}
165
171
173 bUserAssetLibrary *custom_library)
174{
175 return this->get_asset_library_on_disk(
176 ASSET_LIBRARY_CUSTOM, custom_library->name, custom_library->dirpath, true, custom_library);
177}
178
180 StringRefNull root_path)
181{
183 type != ASSET_LIBRARY_CUSTOM,
184 "Use `get_asset_library_on_disk_custom()` for libraries of type `ASSET_LIBRARY_CUSTOM`");
185
186 /* Builtin asset libraries don't need a name, the #eAssetLibraryType is enough to identify them
187 * (and doesn't change, unlike the name). */
188 return this->get_asset_library_on_disk(type, {}, root_path);
189}
190
192{
193 if (current_file_library_) {
194 CLOG_INFO(&LOG, 2, "get current file lib (cached)");
195 current_file_library_->refresh_catalogs();
196 }
197 else {
198 CLOG_INFO(&LOG, 2, "get current file lib (loaded)");
199 current_file_library_ = std::make_unique<RuntimeAssetLibrary>();
200 }
201
202 AssetLibrary *lib = current_file_library_.get();
203 return lib;
204}
205
207{
208 if (all_library_) {
209 all_library_->tag_catalogs_dirty();
210 }
211}
212
214{
215 if (all_library_ && all_library_->is_catalogs_dirty()) {
216 /* Don't reload catalogs from nested libraries from disk, just reflect their currently known
217 * state in the "All" library. Loading catalog changes from disk is only done with a
218 * #AS_asset_library_load()/#AssetLibraryService:get_asset_library() call. */
219 const bool reload_nested_catalogs = false;
220 all_library_->rebuild_catalogs_from_nested(reload_nested_catalogs);
221 }
222}
223
225 const Main &bmain)
226{
227 AssetLibraryService &library_service = *AssetLibraryService::get();
228
229 const std::string root_path = AS_asset_library_find_suitable_root_path_from_main(&bmain);
230 if (root_path.empty()) {
231 return nullptr;
232 }
233
234 BLI_assert_msg(!library_service.lookup_on_disk_library(ASSET_LIBRARY_LOCAL, root_path),
235 "On-disk \"Current File\" asset library shouldn't exist yet, it should only be "
236 "created now in response to initially saving the file - catalog service "
237 "will be overridden");
238
239 /* Create on disk library without loading catalogs. We'll steal the catalog service from the
240 * runtime library below. */
241 AssetLibrary *on_disk_library = library_service.get_asset_library_on_disk(
243 {},
244 root_path,
245 /*load_catalogs=*/false);
246
247 {
248 /* These should always be completely separate, just sanity check since it would cause a
249 * deadlock below. */
250 BLI_assert(on_disk_library != library_service.current_file_library_.get());
251
252 std::lock_guard lock_on_disk{on_disk_library->catalog_service_mutex_};
253 std::lock_guard lock_runtime{library_service.current_file_library_->catalog_service_mutex_};
254 on_disk_library->catalog_service_.swap(
255 library_service.current_file_library_->catalog_service_);
256 }
257
258 AssetCatalogService &catalog_service = on_disk_library->catalog_service();
259 catalog_service.asset_library_root_ = on_disk_library->root_path();
260 /* The catalogs are not stored on disk, so there should not be any CDF. Otherwise, we'd have to
261 * remap their stored file-path too (#AssetCatalogDefinitionFile.file_path). */
262 BLI_assert_msg(catalog_service.get_catalog_definition_file() == nullptr,
263 "new on-disk library shouldn't have catalog definition files - root path "
264 "changed, so they would have to be relocated");
265
266 /* Create a CDF with the runtime catalogs that on-disk catalogs can be merged into. Only do if
267 * there's catalogs to write, otherwise we create empty CDFs on disk on every new .blend save. */
268 if (!catalog_service.catalog_collection_->is_empty()) {
269 char asset_lib_cdf_path[PATH_MAX];
270 BLI_path_join(asset_lib_cdf_path,
271 sizeof(asset_lib_cdf_path),
272 on_disk_library->root_path().c_str(),
274 catalog_service.catalog_collection_->catalog_definition_file_ =
275 catalog_service.construct_cdf_in_memory(asset_lib_cdf_path);
276 }
277
278 library_service.current_file_library_ = nullptr;
279
280 return on_disk_library;
281}
282
284{
285 /* (Re-)load all other asset libraries. */
287 /* Skip self :) */
288 if (library_ref.type == ASSET_LIBRARY_ALL) {
289 continue;
290 }
291
292 /* Ensure all asset libraries are loaded. */
293 this->get_asset_library(bmain, library_ref);
294 }
295
296 if (!all_library_) {
297 CLOG_INFO(&LOG, 2, "get all lib (loaded)");
298 all_library_ = std::make_unique<AllAssetLibrary>();
299 }
300 else {
301 CLOG_INFO(&LOG, 2, "get all lib (cached)");
302 }
303
304 /* Don't reload catalogs, they've just been loaded above. */
305 all_library_->rebuild_catalogs_from_nested(/*reload_nested_catalogs=*/false);
306
307 return all_library_.get();
308}
309
311 StringRefNull root_path)
312{
313 BLI_assert_msg(!root_path.is_empty(),
314 "top level directory must be given for on-disk asset library");
315
316 std::string normalized_root_path = utils::normalize_directory_path(root_path);
317
318 std::unique_ptr<OnDiskAssetLibrary> *lib_uptr_ptr = on_disk_libraries_.lookup_ptr(
319 {library_type, normalized_root_path});
320 return lib_uptr_ptr ? lib_uptr_ptr->get() : nullptr;
321}
322
332
334 StringRef name) const
335{
336 for (const std::unique_ptr<OnDiskAssetLibrary> &library : on_disk_libraries_.values()) {
337 if (library->name_ == name) {
338 return library.get();
339 }
340 }
341 return nullptr;
342}
343
345 const AssetWeakReference &asset_reference)
346{
347 StringRefNull library_dirpath;
348
349 switch (eAssetLibraryType(asset_reference.asset_library_type)) {
352 asset_reference);
353 if (custom_lib) {
354 library_dirpath = custom_lib->dirpath;
355 break;
356 }
357
358 /* A bit of an odd-ball, the API supports loading custom libraries from arbitrary paths (used
359 * by unit tests). So check all loaded on-disk libraries too. */
361 asset_reference.asset_library_identifier);
362 if (!loaded_custom_lib) {
363 return "";
364 }
365
366 library_dirpath = *loaded_custom_lib->root_path_;
367 break;
368 }
370 library_dirpath = essentials_directory_path();
371 break;
374 return "";
375 }
376
377 std::string normalized_library_dirpath = utils::normalize_path(library_dirpath);
378 return normalized_library_dirpath;
379}
380
382{
383 const std::vector<StringRefNull> blendfile_extensions = {".blend" SEP_STR,
384 ".blend.gz" SEP_STR,
385 ".ble" SEP_STR,
386 ".blend" ALTSEP_STR,
387 ".blend.gz" ALTSEP_STR,
388 ".ble" ALTSEP_STR};
389 int64_t blendfile_extension_pos = StringRef::not_found;
390
391 for (StringRefNull blendfile_ext : blendfile_extensions) {
392 const int64_t iter_ext_pos = path.rfind(blendfile_ext);
393 if (iter_ext_pos == StringRef::not_found) {
394 continue;
395 }
396
397 if ((blendfile_extension_pos == StringRef::not_found) ||
398 (blendfile_extension_pos < iter_ext_pos))
399 {
400 blendfile_extension_pos = iter_ext_pos;
401 }
402 }
403
404 return blendfile_extension_pos;
405}
406
408 const AssetWeakReference &asset_reference)
409{
410 StringRefNull relative_asset_identifier = asset_reference.relative_asset_identifier;
411
412 int64_t blend_ext_pos = rfind_blendfile_extension(asset_reference.relative_asset_identifier);
413 const bool has_blend_ext = blend_ext_pos != StringRef::not_found;
414
415 int64_t blend_path_len = 0;
416 /* Get the position of the path separator after the blend file extension. */
417 if (has_blend_ext) {
418 blend_path_len = relative_asset_identifier.find_first_of(SEP_STR ALTSEP_STR, blend_ext_pos);
419
420 /* If there is a blend file in the relative asset path, then there should be group and id name
421 * after it. */
422 BLI_assert(blend_path_len != StringRef::not_found);
423 /* Skip slash. */
424 blend_path_len += 1;
425 }
426
427 /* Find the first path separator (after the blend file extension if any). This will be the one
428 * separating the group from the name. */
429 const int64_t group_name_sep_pos = relative_asset_identifier.find_first_of(SEP_STR ALTSEP_STR,
430 blend_path_len);
431
432 return utils::normalize_path(relative_asset_identifier,
433 (group_name_sep_pos == StringRef::not_found) ?
435 group_name_sep_pos + 1);
436}
437
439 const AssetWeakReference &asset_reference)
440{
441 /* TODO currently only works for asset libraries on disk (custom or essentials asset libraries).
442 * Once there is a proper registry of asset libraries, this could contain an asset library
443 * locator and/or identifier, so a full path (not necessarily file path) can be built for all
444 * asset libraries. */
445
446 if (asset_reference.relative_asset_identifier[0] == '\0') {
447 return "";
448 }
449
450 std::string library_dirpath = resolve_asset_weak_reference_to_library_path(asset_reference);
451 if (library_dirpath.empty()) {
452 return "";
453 }
454
455 std::string normalized_full_path = utils::normalize_path(library_dirpath + SEP_STR) +
457 asset_reference);
458
459 return normalized_full_path;
460}
461
462std::optional<AssetLibraryService::ExplodedPath> AssetLibraryService::
464{
465 if (asset_reference.relative_asset_identifier[0] == '\0') {
466 return std::nullopt;
467 }
468
469 switch (eAssetLibraryType(asset_reference.asset_library_type)) {
470 case ASSET_LIBRARY_LOCAL: {
471 std::string path_in_file = this->normalize_asset_weak_reference_relative_asset_identifier(
472 asset_reference);
473 const int64_t group_len = int64_t(path_in_file.find(SEP));
474
475 ExplodedPath exploded;
476 exploded.full_path = std::make_unique<std::string>(path_in_file);
477 exploded.group_component = StringRef(*exploded.full_path).substr(0, group_len);
478 exploded.name_component = StringRef(*exploded.full_path).substr(group_len + 1);
479
480 return exploded;
481 }
484 std::string full_path = this->resolve_asset_weak_reference_to_full_path(asset_reference);
485 /* #full_path uses native slashes, so others don't need to be considered in the following. */
486
487 if (full_path.empty()) {
488 return std::nullopt;
489 }
490
491 int64_t blendfile_extension_pos = this->rfind_blendfile_extension(full_path);
492 BLI_assert(blendfile_extension_pos != StringRef::not_found);
493
494 size_t group_pos = full_path.find(SEP, blendfile_extension_pos);
495 BLI_assert(group_pos != std::string::npos);
496
497 size_t name_pos = full_path.find(SEP, group_pos + 1);
498 BLI_assert(group_pos != std::string::npos);
499
500 const int64_t dir_len = int64_t(group_pos);
501 const int64_t group_len = int64_t(name_pos - group_pos - 1);
502
503 ExplodedPath exploded;
504 exploded.full_path = std::make_unique<std::string>(full_path);
505 StringRef full_path_ref = *exploded.full_path;
506 exploded.dir_component = full_path_ref.substr(0, dir_len);
507 exploded.group_component = full_path_ref.substr(dir_len + 1, group_len);
508 exploded.name_component = full_path_ref.substr(dir_len + 1 + group_len + 1);
509
510 return exploded;
511 }
513 return std::nullopt;
514 }
515
516 return std::nullopt;
517}
518
527
529 const AssetLibraryReference &library_reference)
530{
531 if (ELEM(library_reference.type, ASSET_LIBRARY_ALL, ASSET_LIBRARY_LOCAL)) {
532 return "";
533 }
534 if (ELEM(library_reference.type, ASSET_LIBRARY_ESSENTIALS)) {
536 }
537
539 library_reference);
540 if (!custom_library || !custom_library->dirpath[0]) {
541 return "";
542 }
543
544 return custom_library->dirpath;
545}
546
548{
549 instance_ = std::make_unique<AssetLibraryService>();
550 instance_->app_handler_register();
551
552 if (!atexit_handler_registered_) {
553 /* Ensure the instance gets freed before Blender's memory leak detector runs. */
554 BKE_blender_atexit_register([](void * /*user_data*/) { AssetLibraryService::destroy(); },
555 nullptr);
556 atexit_handler_registered_ = true;
557 }
558}
559
560static void on_blendfile_load(Main * /*bmain*/,
561 PointerRNA ** /*pointers*/,
562 const int /*num_pointers*/,
563 void * /*arg*/)
564{
565#ifdef WITH_DESTROY_VIA_LOAD_HANDLER
567#endif
568}
569
571{
572 /* The callback system doesn't own `on_load_callback_store_`. */
573 on_load_callback_store_.alloc = false;
574
575 on_load_callback_store_.func = &on_blendfile_load;
576 on_load_callback_store_.arg = this;
577
578 BKE_callback_add(&on_load_callback_store_, BKE_CB_EVT_LOAD_PRE);
579}
580
582{
583 BKE_callback_remove(&on_load_callback_store_, BKE_CB_EVT_LOAD_PRE);
584 on_load_callback_store_.func = nullptr;
585 on_load_callback_store_.arg = nullptr;
586}
587
589{
590 bool has_unsaved_changes = false;
591
593 [&has_unsaved_changes](AssetLibrary &library) {
594 if (library.catalog_service().has_unsaved_changes()) {
595 has_unsaved_changes = true;
596 }
597 },
598 true);
599 return has_unsaved_changes;
600}
601
603 const bool include_all_library) const
604{
605 if (include_all_library && all_library_) {
606 fn(*all_library_);
607 }
608
609 if (current_file_library_) {
610 fn(*current_file_library_);
611 }
612
613 for (const auto &asset_lib_uptr : on_disk_libraries_.values()) {
614 fn(*asset_lib_uptr);
615 }
616}
617
618} // namespace blender::asset_system
std::string AS_asset_library_find_suitable_root_path_from_main(const Main *bmain)
Blender util stuff.
void BKE_blender_atexit_register(void(*func)(void *user_data), void *user_data)
Definition blender.cc:487
void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt)
Definition callbacks.cc:74
void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt)
Definition callbacks.cc:81
@ BKE_CB_EVT_LOAD_PRE
struct bUserAssetLibrary * BKE_preferences_asset_library_find_index(const struct UserDef *userdef, int index) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT
struct bUserAssetLibrary * BKE_preferences_asset_library_find_by_name(const struct UserDef *userdef, const char *name) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
File and directory operations.
#define PATH_MAX
Definition BLI_fileops.h:26
#define BLI_path_join(...)
#define ALTSEP_STR
#define SEP
#define ELEM(...)
#define CLOG_INFO(clg_ref, level,...)
Definition CLG_log.h:179
eAssetImportMethod
@ ASSET_LIBRARY_RELATIVE_PATH
eAssetLibraryType
@ ASSET_LIBRARY_CUSTOM
@ ASSET_LIBRARY_ESSENTIALS
@ ASSET_LIBRARY_LOCAL
@ ASSET_LIBRARY_ALL
#define U
long long int int64_t
static constexpr int64_t not_found
constexpr int64_t rfind(char c, int64_t pos=INT64_MAX) const
constexpr bool is_empty() const
constexpr StringRef substr(int64_t start, int64_t size) const
constexpr int64_t find_first_of(StringRef chars, int64_t pos=0) const
constexpr const char * c_str() const
std::unique_ptr< AssetCatalogDefinitionFile > construct_cdf_in_memory(const CatalogFilePath &file_path) const
static const CatalogFilePath DEFAULT_CATALOG_FILENAME
const AssetCatalogDefinitionFile * get_catalog_definition_file() const
void foreach_loaded_asset_library(FunctionRef< void(AssetLibrary &)> fn, bool include_all_library) const
AssetLibrary * get_asset_library_on_disk_builtin(eAssetLibraryType type, StringRefNull root_path)
AssetLibrary * get_asset_library_on_disk_custom_preferences(bUserAssetLibrary *custom_library)
AssetLibrary * get_asset_library(const Main *bmain, const AssetLibraryReference &library_reference)
AssetLibrary * find_loaded_on_disk_asset_library_from_name(StringRef name) const
static bUserAssetLibrary * find_custom_asset_library_from_library_ref(const AssetLibraryReference &library_reference)
OnDiskAssetLibrary * lookup_on_disk_library(eAssetLibraryType type, StringRefNull root_path)
std::optional< ExplodedPath > resolve_asset_weak_reference_to_exploded_path(const AssetWeakReference &asset_reference)
static AssetLibrary * move_runtime_current_file_into_on_disk_library(const Main &bmain)
std::string resolve_asset_weak_reference_to_full_path(const AssetWeakReference &asset_reference)
AssetLibrary * get_asset_library_on_disk_custom(StringRef name, StringRefNull root_path)
static std::string root_path_from_library_ref(const AssetLibraryReference &library_reference)
std::string normalize_asset_weak_reference_relative_asset_identifier(const AssetWeakReference &asset_reference)
static bUserAssetLibrary * find_custom_preferences_asset_library_from_asset_weak_ref(const AssetWeakReference &asset_reference)
std::string resolve_asset_weak_reference_to_library_path(const AssetWeakReference &asset_reference)
AssetLibrary * get_asset_library_on_disk(eAssetLibraryType library_type, StringRef name, StringRefNull root_path, bool load_catalogs=true, bUserAssetLibrary *preferences_library=nullptr)
AssetLibrary * get_asset_library_all(const Main *bmain)
std::optional< eAssetImportMethod > import_method_
std::unique_ptr< AssetCatalogService > catalog_service_
AssetCatalogService & catalog_service() const
#define LOG(severity)
Definition log.h:32
std::string normalize_path(StringRefNull path, int64_t max_len)
std::string normalize_directory_path(StringRef directory)
Vector< AssetLibraryReference > all_valid_asset_library_refs()
static void on_blendfile_load(Main *, PointerRNA **, const int, void *)
StringRefNull essentials_directory_path()
const char * relative_asset_identifier
const char * asset_library_identifier
#define SEP_STR
Definition unit.cc:39
static DynamicLibrary lib