Blender V4.3
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
9#include "BKE_blender.hh"
10#include "BKE_preferences.h"
11
12#include "BLI_path_utils.hh"
13#include "BLI_string_ref.hh"
14
15#include "DNA_asset_types.h"
16#include "DNA_userdef_types.h"
17
18#include "CLG_log.h"
19
20#include "AS_asset_library.hh"
22#include "asset_library_all.hh"
28#include "utils.hh"
29
30/* When enabled, use a pre file load handler (#BKE_CB_EVT_LOAD_PRE) callback to destroy the asset
31 * library service. Without this an explicit call from the file loading code is needed to do this,
32 * which is not as nice.
33 *
34 * TODO Currently disabled because UI data depends on asset library data, so we have to make sure
35 * it's freed in the right order (UI first). Pre-load handlers don't give us this order.
36 * Should be addressed with a proper ownership model for the asset system:
37 * https://developer.blender.org/docs/features/asset_system/backend/#ownership-model
38 */
39// #define WITH_DESTROY_VIA_LOAD_HANDLER
40
41static CLG_LogRef LOG = {"asset_system.asset_library_service"};
42
43namespace blender::asset_system {
44
45std::unique_ptr<AssetLibraryService> AssetLibraryService::instance_;
46bool AssetLibraryService::atexit_handler_registered_ = false;
47
49{
50 if (!instance_) {
52 }
53 return instance_.get();
54}
55
57{
58 if (!instance_) {
59 return;
60 }
61 instance_->app_handler_unregister();
62 instance_.reset();
63}
64
66 const Main *bmain, const AssetLibraryReference &library_reference)
67{
68 const eAssetLibraryType type = eAssetLibraryType(library_reference.type);
69
70 switch (type) {
72 const StringRefNull root_path = essentials_directory_path();
73 if (root_path.is_empty()) {
74 return nullptr;
75 }
76
77 return this->get_asset_library_on_disk_builtin(type, root_path);
78 }
80 /* For the "Current File" library we get the asset library root path based on main. */
81 std::string root_path = bmain ? AS_asset_library_find_suitable_root_path_from_main(bmain) :
82 "";
83
84 if (root_path.empty()) {
85 /* File wasn't saved yet. */
86 return this->get_asset_library_current_file();
87 }
88 return this->get_asset_library_on_disk_builtin(type, root_path);
89 }
91 return this->get_asset_library_all(bmain);
94 library_reference);
95 if (!custom_library) {
96 return nullptr;
97 }
98
99 std::string root_path = custom_library->dirpath;
100 if (root_path.empty()) {
101 return nullptr;
102 }
103
104 AssetLibrary *library = this->get_asset_library_on_disk_custom(custom_library->name,
105 root_path);
106 library->import_method_ = eAssetImportMethod(custom_library->import_method);
107 library->may_override_import_method_ = true;
108 library->use_relative_path_ = (custom_library->flag & ASSET_LIBRARY_RELATIVE_PATH) != 0;
109
110 return library;
111 }
112 }
113
114 return nullptr;
115}
116
118 StringRef name,
119 StringRefNull root_path)
120{
121 BLI_assert_msg(!root_path.is_empty(),
122 "top level directory must be given for on-disk asset library");
123
124 std::string normalized_root_path = utils::normalize_directory_path(root_path);
125
126 std::unique_ptr<OnDiskAssetLibrary> *lib_uptr_ptr = on_disk_libraries_.lookup_ptr(
127 {library_type, normalized_root_path});
128 if (lib_uptr_ptr != nullptr) {
129 CLOG_INFO(&LOG, 2, "get \"%s\" (cached)", normalized_root_path.c_str());
130 AssetLibrary *lib = lib_uptr_ptr->get();
131 lib->refresh_catalogs();
132 return lib;
133 }
134
135 std::unique_ptr<OnDiskAssetLibrary> lib_uptr;
136 switch (library_type) {
138 lib_uptr = std::make_unique<PreferencesOnDiskAssetLibrary>(name, normalized_root_path);
139 break;
141 lib_uptr = std::make_unique<EssentialsAssetLibrary>();
142 break;
143 default:
144 lib_uptr = std::make_unique<OnDiskAssetLibrary>(library_type, name, normalized_root_path);
145 break;
146 }
147
148 AssetLibrary *lib = lib_uptr.get();
149
150 lib->load_catalogs();
151
152 on_disk_libraries_.add_new({library_type, normalized_root_path}, std::move(lib_uptr));
153 CLOG_INFO(&LOG, 2, "get \"%s\" (loaded)", normalized_root_path.c_str());
154 return lib;
155}
156
162
164 StringRefNull root_path)
165{
167 type != ASSET_LIBRARY_CUSTOM,
168 "Use `get_asset_library_on_disk_custom()` for libraries of type `ASSET_LIBRARY_CUSTOM`");
169
170 /* Builtin asset libraries don't need a name, the #eAssetLibraryType is enough to identify them
171 * (and doesn't change, unlike the name). */
172 return this->get_asset_library_on_disk(type, {}, root_path);
173}
174
176{
177 if (current_file_library_) {
178 CLOG_INFO(&LOG, 2, "get current file lib (cached)");
179 current_file_library_->refresh_catalogs();
180 }
181 else {
182 CLOG_INFO(&LOG, 2, "get current file lib (loaded)");
183 current_file_library_ = std::make_unique<RuntimeAssetLibrary>();
184 }
185
186 AssetLibrary *lib = current_file_library_.get();
187 return lib;
188}
189
191{
192 if (all_library_) {
193 all_library_->tag_catalogs_dirty();
194 }
195}
196
198{
199 if (all_library_ && all_library_->is_catalogs_dirty()) {
200 /* Don't reload catalogs from nested libraries from disk, just reflect their currently known
201 * state in the "All" library. Loading catalog changes from disk is only done with a
202 * #AS_asset_library_load()/#AssetLibraryService:get_asset_library() call. */
203 const bool reload_nested_catalogs = false;
204 all_library_->rebuild_catalogs_from_nested(reload_nested_catalogs);
205 }
206}
207
209{
210 /* (Re-)load all other asset libraries. */
212 /* Skip self :) */
213 if (library_ref.type == ASSET_LIBRARY_ALL) {
214 continue;
215 }
216
217 /* Ensure all asset libraries are loaded. */
218 this->get_asset_library(bmain, library_ref);
219 }
220
221 if (!all_library_) {
222 CLOG_INFO(&LOG, 2, "get all lib (loaded)");
223 all_library_ = std::make_unique<AllAssetLibrary>();
224 }
225 else {
226 CLOG_INFO(&LOG, 2, "get all lib (cached)");
227 }
228
229 /* Don't reload catalogs, they've just been loaded above. */
230 all_library_->rebuild_catalogs_from_nested(/*reload_nested_catalogs=*/false);
231
232 return all_library_.get();
233}
234
244
246 StringRef name) const
247{
248 for (const std::unique_ptr<OnDiskAssetLibrary> &library : on_disk_libraries_.values()) {
249 if (library->name_ == name) {
250 return library.get();
251 }
252 }
253 return nullptr;
254}
255
257 const AssetWeakReference &asset_reference)
258{
259 StringRefNull library_dirpath;
260
261 switch (eAssetLibraryType(asset_reference.asset_library_type)) {
263 bUserAssetLibrary *custom_lib =
265 if (custom_lib) {
266 library_dirpath = custom_lib->dirpath;
267 break;
268 }
269
270 /* A bit of an odd-ball, the API supports loading custom libraries from arbitrary paths (used
271 * by unit tests). So check all loaded on-disk libraries too. */
273 asset_reference.asset_library_identifier);
274 if (!loaded_custom_lib) {
275 return "";
276 }
277
278 library_dirpath = *loaded_custom_lib->root_path_;
279 break;
280 }
282 library_dirpath = essentials_directory_path();
283 break;
286 return "";
287 }
288
289 std::string normalized_library_dirpath = utils::normalize_path(library_dirpath);
290 return normalized_library_dirpath;
291}
292
294{
295 const std::vector<StringRefNull> blendfile_extensions = {".blend" SEP_STR,
296 ".blend.gz" SEP_STR,
297 ".ble" SEP_STR,
298 ".blend" ALTSEP_STR,
299 ".blend.gz" ALTSEP_STR,
300 ".ble" ALTSEP_STR};
301 int64_t blendfile_extension_pos = StringRef::not_found;
302
303 for (StringRefNull blendfile_ext : blendfile_extensions) {
304 const int64_t iter_ext_pos = path.rfind(blendfile_ext);
305 if (iter_ext_pos == StringRef::not_found) {
306 continue;
307 }
308
309 if ((blendfile_extension_pos == StringRef::not_found) ||
310 (blendfile_extension_pos < iter_ext_pos))
311 {
312 blendfile_extension_pos = iter_ext_pos;
313 }
314 }
315
316 return blendfile_extension_pos;
317}
318
320 const AssetWeakReference &asset_reference)
321{
322 StringRefNull relative_asset_identifier = asset_reference.relative_asset_identifier;
323
324 int64_t blend_ext_pos = rfind_blendfile_extension(asset_reference.relative_asset_identifier);
325 const bool has_blend_ext = blend_ext_pos != StringRef::not_found;
326
327 int64_t blend_path_len = 0;
328 /* Get the position of the path separator after the blend file extension. */
329 if (has_blend_ext) {
330 blend_path_len = relative_asset_identifier.find_first_of(SEP_STR ALTSEP_STR, blend_ext_pos);
331
332 /* If there is a blend file in the relative asset path, then there should be group and id name
333 * after it. */
334 BLI_assert(blend_path_len != StringRef::not_found);
335 /* Skip slash. */
336 blend_path_len += 1;
337 }
338
339 /* Find the first path separator (after the blend file extension if any). This will be the one
340 * separating the group from the name. */
341 const int64_t group_name_sep_pos = relative_asset_identifier.find_first_of(SEP_STR ALTSEP_STR,
342 blend_path_len);
343
344 return utils::normalize_path(relative_asset_identifier,
345 (group_name_sep_pos == StringRef::not_found) ?
347 group_name_sep_pos + 1);
348}
349
351 const AssetWeakReference &asset_reference)
352{
353 /* TODO currently only works for asset libraries on disk (custom or essentials asset libraries).
354 * Once there is a proper registry of asset libraries, this could contain an asset library
355 * locator and/or identifier, so a full path (not necessarily file path) can be built for all
356 * asset libraries. */
357
358 if (asset_reference.relative_asset_identifier[0] == '\0') {
359 return "";
360 }
361
362 std::string library_dirpath = resolve_asset_weak_reference_to_library_path(asset_reference);
363 if (library_dirpath.empty()) {
364 return "";
365 }
366
367 std::string normalized_full_path = utils::normalize_path(library_dirpath + SEP_STR) +
369 asset_reference);
370
371 return normalized_full_path;
372}
373
374std::optional<AssetLibraryService::ExplodedPath> AssetLibraryService::
376{
377 if (asset_reference.relative_asset_identifier[0] == '\0') {
378 return std::nullopt;
379 }
380
381 switch (eAssetLibraryType(asset_reference.asset_library_type)) {
382 case ASSET_LIBRARY_LOCAL: {
383 std::string path_in_file = this->normalize_asset_weak_reference_relative_asset_identifier(
384 asset_reference);
385 const int64_t group_len = int64_t(path_in_file.find(SEP));
386
387 ExplodedPath exploded;
388 exploded.full_path = std::make_unique<std::string>(path_in_file);
389 exploded.group_component = StringRef(*exploded.full_path).substr(0, group_len);
390 exploded.name_component = StringRef(*exploded.full_path).substr(group_len + 1);
391
392 return exploded;
393 }
396 std::string full_path = this->resolve_asset_weak_reference_to_full_path(asset_reference);
397 /* #full_path uses native slashes, so others don't need to be considered in the following. */
398
399 if (full_path.empty()) {
400 return std::nullopt;
401 }
402
403 int64_t blendfile_extension_pos = this->rfind_blendfile_extension(full_path);
404 BLI_assert(blendfile_extension_pos != StringRef::not_found);
405
406 size_t group_pos = full_path.find(SEP, blendfile_extension_pos);
407 BLI_assert(group_pos != std::string::npos);
408
409 size_t name_pos = full_path.find(SEP, group_pos + 1);
410 BLI_assert(group_pos != std::string::npos);
411
412 const int64_t dir_len = int64_t(group_pos);
413 const int64_t group_len = int64_t(name_pos - group_pos - 1);
414
415 ExplodedPath exploded;
416 exploded.full_path = std::make_unique<std::string>(full_path);
417 StringRef full_path_ref = *exploded.full_path;
418 exploded.dir_component = full_path_ref.substr(0, dir_len);
419 exploded.group_component = full_path_ref.substr(dir_len + 1, group_len);
420 exploded.name_component = full_path_ref.substr(dir_len + 1 + group_len + 1);
421
422 return exploded;
423 }
425 return std::nullopt;
426 }
427
428 return std::nullopt;
429}
430
439
441 const AssetLibraryReference &library_reference)
442{
443 if (ELEM(library_reference.type, ASSET_LIBRARY_ALL, ASSET_LIBRARY_LOCAL)) {
444 return "";
445 }
446 if (ELEM(library_reference.type, ASSET_LIBRARY_ESSENTIALS)) {
448 }
449
451 library_reference);
452 if (!custom_library || !custom_library->dirpath[0]) {
453 return "";
454 }
455
456 return custom_library->dirpath;
457}
458
460{
461 instance_ = std::make_unique<AssetLibraryService>();
462 instance_->app_handler_register();
463
464 if (!atexit_handler_registered_) {
465 /* Ensure the instance gets freed before Blender's memory leak detector runs. */
466 BKE_blender_atexit_register([](void * /*user_data*/) { AssetLibraryService::destroy(); },
467 nullptr);
468 atexit_handler_registered_ = true;
469 }
470}
471
472static void on_blendfile_load(Main * /*bmain*/,
473 PointerRNA ** /*pointers*/,
474 const int /*num_pointers*/,
475 void * /*arg*/)
476{
477#ifdef WITH_DESTROY_VIA_LOAD_HANDLER
479#endif
480}
481
483{
484 /* The callback system doesn't own `on_load_callback_store_`. */
485 on_load_callback_store_.alloc = false;
486
487 on_load_callback_store_.func = &on_blendfile_load;
488 on_load_callback_store_.arg = this;
489
490 BKE_callback_add(&on_load_callback_store_, BKE_CB_EVT_LOAD_PRE);
491}
492
494{
495 BKE_callback_remove(&on_load_callback_store_, BKE_CB_EVT_LOAD_PRE);
496 on_load_callback_store_.func = nullptr;
497 on_load_callback_store_.arg = nullptr;
498}
499
501{
502 bool has_unsaved_changes = false;
503
505 [&has_unsaved_changes](AssetLibrary &library) {
506 if (library.catalog_service().has_unsaved_changes()) {
507 has_unsaved_changes = true;
508 }
509 },
510 true);
511 return has_unsaved_changes;
512}
513
515 const bool include_all_library) const
516{
517 if (include_all_library && all_library_) {
518 fn(*all_library_);
519 }
520
521 if (current_file_library_) {
522 fn(*current_file_library_);
523 }
524
525 for (const auto &asset_lib_uptr : on_disk_libraries_.values()) {
526 fn(*asset_lib_uptr);
527 }
528}
529
530} // 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:466
void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt)
Definition callbacks.cc:75
void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt)
Definition callbacks.cc:82
@ 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:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
#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
static CLG_LogRef LOG
unsigned int U
Definition btGjkEpa3.h:78
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
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(const Main *bmain, const AssetLibraryReference &library_reference)
AssetLibrary * find_loaded_on_disk_asset_library_from_name(StringRef name) const
AssetLibrary * get_asset_library_on_disk(eAssetLibraryType library_type, StringRef name, StringRefNull root_path)
static bUserAssetLibrary * find_custom_asset_library_from_library_ref(const AssetLibraryReference &library_reference)
std::optional< ExplodedPath > resolve_asset_weak_reference_to_exploded_path(const AssetWeakReference &asset_reference)
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_all(const Main *bmain)
std::optional< eAssetImportMethod > import_method_
AssetCatalogService & catalog_service() const
#define LOG(severity)
Definition log.h:33
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()
__int64 int64_t
Definition stdint.h:89
const char * relative_asset_identifier
const char * asset_library_identifier
void(* func)(Main *, PointerRNA **, int num_pointers, void *arg)
#define SEP_STR
Definition unit.cc:39
static DynamicLibrary lib