Blender V5.0
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.library"};
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,
122 StringRefNull root_path,
123 const bool load_catalogs,
124 bUserAssetLibrary *preferences_library)
125{
126 if (OnDiskAssetLibrary *lib = this->lookup_on_disk_library(library_type, root_path)) {
127 CLOG_DEBUG(&LOG, "get \"%s\" (cached)", root_path.c_str());
128 if (load_catalogs) {
129 lib->load_or_reload_catalogs();
130 }
131 return lib;
132 }
133
134 const std::string normalized_root_path = utils::normalize_directory_path(root_path);
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 AssetLibrary *lib = lib_uptr.get();
155
156 if (load_catalogs) {
157 lib->load_or_reload_catalogs();
158 }
159
160 on_disk_libraries_.add_new({library_type, normalized_root_path}, std::move(lib_uptr));
161 CLOG_DEBUG(&LOG, "get \"%s\" (loaded)", normalized_root_path.c_str());
162 return lib;
163}
164
170
172 bUserAssetLibrary *custom_library)
173{
174 return this->get_asset_library_on_disk(
175 ASSET_LIBRARY_CUSTOM, custom_library->name, custom_library->dirpath, true, custom_library);
176}
177
179 StringRefNull root_path)
180{
182 type != ASSET_LIBRARY_CUSTOM,
183 "Use `get_asset_library_on_disk_custom()` for libraries of type `ASSET_LIBRARY_CUSTOM`");
184
185 /* Builtin asset libraries don't need a name, the #eAssetLibraryType is enough to identify them
186 * (and doesn't change, unlike the name). */
187 return this->get_asset_library_on_disk(type, {}, root_path);
188}
189
191{
192 if (current_file_library_) {
193 CLOG_DEBUG(&LOG, "get current file lib (cached)");
194 current_file_library_->refresh_catalogs();
195 }
196 else {
197 CLOG_DEBUG(&LOG, "get current file lib (loaded)");
198 current_file_library_ = std::make_unique<RuntimeAssetLibrary>();
199 }
200
201 AssetLibrary *lib = current_file_library_.get();
202 return lib;
203}
204
206{
207 if (all_library_) {
208 all_library_->tag_catalogs_dirty();
209 }
210}
211
213{
214 if (all_library_ && all_library_->is_catalogs_dirty()) {
215 /* Don't reload catalogs from nested libraries from disk, just reflect their currently known
216 * state in the "All" library. Loading catalog changes from disk is only done with a
217 * #AS_asset_library_load()/#AssetLibraryService:get_asset_library() call. */
218 const bool reload_nested_catalogs = false;
219 all_library_->rebuild_catalogs_from_nested(reload_nested_catalogs);
220 }
221}
222
224 const Main &bmain)
225{
226 AssetLibraryService &library_service = *AssetLibraryService::get();
227
228 const std::string root_path = AS_asset_library_find_suitable_root_path_from_main(&bmain);
229 if (root_path.empty()) {
230 return nullptr;
231 }
232
233 BLI_assert_msg(!library_service.lookup_on_disk_library(ASSET_LIBRARY_LOCAL, root_path),
234 "On-disk \"Current File\" asset library shouldn't exist yet, it should only be "
235 "created now in response to initially saving the file - catalog service "
236 "will be overridden");
237
238 /* Create on disk library without loading catalogs. We'll steal the catalog service from the
239 * runtime library below. */
240 AssetLibrary *on_disk_library = library_service.get_asset_library_on_disk(
242 {},
243 root_path,
244 /*load_catalogs=*/false);
245
246 {
247 /* These should always be completely separate, just sanity check since it would cause a
248 * deadlock below. */
249 BLI_assert(on_disk_library != library_service.current_file_library_.get());
250
251 std::lock_guard lock_on_disk{on_disk_library->catalog_service_mutex_};
252 std::lock_guard lock_runtime{library_service.current_file_library_->catalog_service_mutex_};
253 on_disk_library->catalog_service_.swap(
254 library_service.current_file_library_->catalog_service_);
255 }
256
257 AssetCatalogService &catalog_service = on_disk_library->catalog_service();
258 catalog_service.asset_library_root_ = on_disk_library->root_path();
259 /* The catalogs are not stored on disk, so there should not be any CDF. Otherwise, we'd have to
260 * remap their stored file-path too (#AssetCatalogDefinitionFile.file_path). */
261 BLI_assert_msg(catalog_service.get_catalog_definition_file() == nullptr,
262 "new on-disk library shouldn't have catalog definition files - root path "
263 "changed, so they would have to be relocated");
264
265 /* Create a CDF with the runtime catalogs that on-disk catalogs can be merged into. Only do if
266 * there's catalogs to write, otherwise we create empty CDFs on disk on every new .blend save. */
267 if (!catalog_service.catalog_collection_->is_empty()) {
268 char asset_lib_cdf_path[PATH_MAX];
269 BLI_path_join(asset_lib_cdf_path,
270 sizeof(asset_lib_cdf_path),
271 on_disk_library->root_path().c_str(),
273 catalog_service.catalog_collection_->catalog_definition_file_ =
274 catalog_service.construct_cdf_in_memory(asset_lib_cdf_path);
275 }
276
277 library_service.current_file_library_ = nullptr;
278
279 return on_disk_library;
280}
281
283{
284 /* (Re-)load all other asset libraries. */
286 /* Skip self :) */
287 if (library_ref.type == ASSET_LIBRARY_ALL) {
288 continue;
289 }
290
291 /* Ensure all asset libraries are loaded. */
292 this->get_asset_library(bmain, library_ref);
293 }
294
295 if (!all_library_) {
296 CLOG_DEBUG(&LOG, "get all lib (loaded)");
297 all_library_ = std::make_unique<AllAssetLibrary>();
298 }
299 else {
300 CLOG_DEBUG(&LOG, "get all lib (cached)");
301 }
302
303 /* Don't reload catalogs, they've just been loaded above. */
304 all_library_->rebuild_catalogs_from_nested(/*reload_nested_catalogs=*/false);
305
306 return all_library_.get();
307}
308
310 StringRefNull root_path)
311{
312 BLI_assert_msg(!root_path.is_empty(),
313 "top level directory must be given for on-disk asset library");
314
315 std::string normalized_root_path = utils::normalize_directory_path(root_path);
316
317 std::unique_ptr<OnDiskAssetLibrary> *lib_uptr_ptr = on_disk_libraries_.lookup_ptr(
318 {library_type, normalized_root_path});
319 return lib_uptr_ptr ? lib_uptr_ptr->get() : nullptr;
320}
321
331
333 StringRef name) const
334{
335 for (const std::unique_ptr<OnDiskAssetLibrary> &library : on_disk_libraries_.values()) {
336 if (library->name_ == name) {
337 return library.get();
338 }
339 }
340 return nullptr;
341}
342
344 const AssetWeakReference &asset_reference)
345{
346 StringRefNull library_dirpath;
347
348 switch (eAssetLibraryType(asset_reference.asset_library_type)) {
351 asset_reference);
352 if (custom_lib) {
353 library_dirpath = custom_lib->dirpath;
354 break;
355 }
356
357 /* A bit of an odd-ball, the API supports loading custom libraries from arbitrary paths (used
358 * by unit tests). So check all loaded on-disk libraries too. */
360 asset_reference.asset_library_identifier);
361 if (!loaded_custom_lib) {
362 return "";
363 }
364
365 library_dirpath = *loaded_custom_lib->root_path_;
366 break;
367 }
369 library_dirpath = essentials_directory_path();
370 break;
373 return "";
374 }
375
376 std::string normalized_library_dirpath = utils::normalize_path(library_dirpath);
377 return normalized_library_dirpath;
378}
379
381{
382 const std::vector<StringRefNull> blendfile_extensions = {".blend" SEP_STR,
383 ".blend.gz" SEP_STR,
384 ".ble" SEP_STR,
385 ".blend" ALTSEP_STR,
386 ".blend.gz" ALTSEP_STR,
387 ".ble" ALTSEP_STR};
388 int64_t blendfile_extension_pos = StringRef::not_found;
389
390 for (StringRefNull blendfile_ext : blendfile_extensions) {
391 const int64_t iter_ext_pos = path.rfind(blendfile_ext);
392 if (iter_ext_pos == StringRef::not_found) {
393 continue;
394 }
395
396 if ((blendfile_extension_pos == StringRef::not_found) ||
397 (blendfile_extension_pos < iter_ext_pos))
398 {
399 blendfile_extension_pos = iter_ext_pos;
400 }
401 }
402
403 return blendfile_extension_pos;
404}
405
407 const AssetWeakReference &asset_reference)
408{
409 StringRefNull relative_asset_identifier = asset_reference.relative_asset_identifier;
410
411 int64_t blend_ext_pos = rfind_blendfile_extension(asset_reference.relative_asset_identifier);
412 const bool has_blend_ext = blend_ext_pos != StringRef::not_found;
413
414 int64_t blend_path_len = 0;
415 /* Get the position of the path separator after the blend file extension. */
416 if (has_blend_ext) {
417 blend_path_len = relative_asset_identifier.find_first_of(SEP_STR ALTSEP_STR, blend_ext_pos);
418
419 /* If there is a blend file in the relative asset path, then there should be group and id name
420 * after it. */
421 BLI_assert(blend_path_len != StringRef::not_found);
422 /* Skip slash. */
423 blend_path_len += 1;
424 }
425
426 /* Find the first path separator (after the blend file extension if any). This will be the one
427 * separating the group from the name. */
428 const int64_t group_name_sep_pos = relative_asset_identifier.find_first_of(SEP_STR ALTSEP_STR,
429 blend_path_len);
430
431 return utils::normalize_path(relative_asset_identifier,
432 (group_name_sep_pos == StringRef::not_found) ?
434 group_name_sep_pos + 1);
435}
436
438 const AssetWeakReference &asset_reference)
439{
440 /* TODO currently only works for asset libraries on disk (custom or essentials asset libraries).
441 * Once there is a proper registry of asset libraries, this could contain an asset library
442 * locator and/or identifier, so a full path (not necessarily file path) can be built for all
443 * asset libraries. */
444
445 if (asset_reference.relative_asset_identifier[0] == '\0') {
446 return "";
447 }
448
449 std::string library_dirpath = resolve_asset_weak_reference_to_library_path(asset_reference);
450 if (library_dirpath.empty()) {
451 return "";
452 }
453
454 std::string normalized_full_path = utils::normalize_path(library_dirpath + SEP_STR) +
456 asset_reference);
457
458 return normalized_full_path;
459}
460
461std::optional<AssetLibraryService::ExplodedPath> AssetLibraryService::
463{
464 if (asset_reference.relative_asset_identifier[0] == '\0') {
465 return std::nullopt;
466 }
467
468 switch (eAssetLibraryType(asset_reference.asset_library_type)) {
469 case ASSET_LIBRARY_LOCAL: {
470 std::string path_in_file = this->normalize_asset_weak_reference_relative_asset_identifier(
471 asset_reference);
472 const int64_t group_len = int64_t(path_in_file.find(SEP));
473
474 ExplodedPath exploded;
475 exploded.full_path = std::make_unique<std::string>(path_in_file);
476 exploded.group_component = StringRef(*exploded.full_path).substr(0, group_len);
477 exploded.name_component = StringRef(*exploded.full_path).substr(group_len + 1);
478
479 return exploded;
480 }
483 std::string full_path = this->resolve_asset_weak_reference_to_full_path(asset_reference);
484 /* #full_path uses native slashes, so others don't need to be considered in the following. */
485
486 if (full_path.empty()) {
487 return std::nullopt;
488 }
489
490 int64_t blendfile_extension_pos = this->rfind_blendfile_extension(full_path);
491 BLI_assert(blendfile_extension_pos != StringRef::not_found);
492
493 size_t group_pos = full_path.find(SEP, blendfile_extension_pos);
494 BLI_assert(group_pos != std::string::npos);
495
496 size_t name_pos = full_path.find(SEP, group_pos + 1);
497 BLI_assert(group_pos != std::string::npos);
498
499 const int64_t dir_len = int64_t(group_pos);
500 const int64_t group_len = int64_t(name_pos - group_pos - 1);
501
502 ExplodedPath exploded;
503 exploded.full_path = std::make_unique<std::string>(full_path);
504 StringRef full_path_ref = *exploded.full_path;
505 exploded.dir_component = full_path_ref.substr(0, dir_len);
506 exploded.group_component = full_path_ref.substr(dir_len + 1, group_len);
507 exploded.name_component = full_path_ref.substr(dir_len + 1 + group_len + 1);
508
509 return exploded;
510 }
512 return std::nullopt;
513 }
514
515 return std::nullopt;
516}
517
526
528 const AssetLibraryReference &library_reference)
529{
530 if (ELEM(library_reference.type, ASSET_LIBRARY_ALL, ASSET_LIBRARY_LOCAL)) {
531 return "";
532 }
533 if (ELEM(library_reference.type, ASSET_LIBRARY_ESSENTIALS)) {
535 }
536
538 library_reference);
539 if (!custom_library || !custom_library->dirpath[0]) {
540 return "";
541 }
542
543 return custom_library->dirpath;
544}
545
547{
548 instance_ = std::make_unique<AssetLibraryService>();
549 instance_->app_handler_register();
550
551 if (!atexit_handler_registered_) {
552 /* Ensure the instance gets freed before Blender's memory leak detector runs. */
553 BKE_blender_atexit_register([](void * /*user_data*/) { AssetLibraryService::destroy(); },
554 nullptr);
555 atexit_handler_registered_ = true;
556 }
557}
558
559static void on_blendfile_load(Main * /*bmain*/,
560 PointerRNA ** /*pointers*/,
561 const int /*num_pointers*/,
562 void * /*arg*/)
563{
564#ifdef WITH_DESTROY_VIA_LOAD_HANDLER
566#endif
567}
568
570{
571 /* The callback system doesn't own `on_load_callback_store_`. */
572 on_load_callback_store_.alloc = false;
573
574 on_load_callback_store_.func = &on_blendfile_load;
575 on_load_callback_store_.arg = this;
576
577 BKE_callback_add(&on_load_callback_store_, BKE_CB_EVT_LOAD_PRE);
578}
579
581{
582 BKE_callback_remove(&on_load_callback_store_, BKE_CB_EVT_LOAD_PRE);
583 on_load_callback_store_.func = nullptr;
584 on_load_callback_store_.arg = nullptr;
585}
586
588{
589 bool has_unsaved_changes = false;
590
592 [&has_unsaved_changes](AssetLibrary &library) {
593 if (library.catalog_service().has_unsaved_changes()) {
594 has_unsaved_changes = true;
595 }
596 },
597 true);
598 return has_unsaved_changes;
599}
600
602 const bool include_all_library) const
603{
604 if (include_all_library && all_library_) {
605 fn(*all_library_);
606 }
607
608 if (current_file_library_) {
609 fn(*current_file_library_);
610 }
611
612 for (const auto &asset_lib_uptr : on_disk_libraries_.values()) {
613 fn(*asset_lib_uptr);
614 }
615}
616
617} // 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:490
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_DEBUG(clg_ref,...)
Definition CLG_log.h:191
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(level)
Definition log.h:97
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 * name
const char * relative_asset_identifier
const char * asset_library_identifier
#define SEP_STR
Definition unit.cc:39
static DynamicLibrary lib