Blender V4.3
preferences.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
11#include <cstring>
12
13#include "MEM_guardedalloc.h"
14
15#include "BLI_fileops.h"
16#include "BLI_listbase.h"
17#include "BLI_path_utils.hh"
18#include "BLI_string.h"
19#include "BLI_string_utf8.h"
20#include "BLI_string_utils.hh"
21
22#include "BKE_appdir.hh"
23#include "BKE_asset.hh"
24#include "BKE_preferences.h"
25
26#include "BLT_translation.hh"
27
28#include "BLO_read_write.hh"
29
30#include "DNA_asset_types.h"
31#include "DNA_defaults.h"
32#include "DNA_userdef_types.h"
33
34#define U BLI_STATIC_ASSERT(false, "Global 'U' not allowed, only use arguments passed in!")
35
36/* -------------------------------------------------------------------- */
41 const char *name,
42 const char *dirpath)
43{
45
46 BLI_addtail(&userdef->asset_libraries, library);
47
48 if (name) {
49 BKE_preferences_asset_library_name_set(userdef, library, name);
50 }
51 if (dirpath) {
52 STRNCPY(library->dirpath, dirpath);
53 }
54
55 return library;
56}
57
59{
60 BLI_freelinkN(&userdef->asset_libraries, library);
61}
62
64 bUserAssetLibrary *library,
65 const char *name)
66{
67 STRNCPY_UTF8(library->name, name);
69 library,
70 name,
71 '.',
73 sizeof(library->name));
74}
75
77{
78 STRNCPY(library->dirpath, path);
79 if (BLI_is_file(library->dirpath)) {
81 }
82}
83
85{
86 return static_cast<bUserAssetLibrary *>(BLI_findlink(&userdef->asset_libraries, index));
87}
88
90 const char *name)
91{
92 return static_cast<bUserAssetLibrary *>(
94}
95
97 const char *path)
98{
99 LISTBASE_FOREACH (bUserAssetLibrary *, asset_lib_pref, &userdef->asset_libraries) {
100 if (BLI_path_contains(asset_lib_pref->dirpath, path)) {
101 return asset_lib_pref;
102 }
103 }
104 return nullptr;
105}
106
108 const bUserAssetLibrary *library)
109{
110 return BLI_findindex(&userdef->asset_libraries, library);
111}
112
114{
115 char documents_path[FILE_MAXDIR];
116
117 /* No home or documents path found, not much we can do. */
118 if (!BKE_appdir_folder_documents(documents_path) || !documents_path[0]) {
119 return;
120 }
121
124
125 /* Add new "Default" library under '[doc_path]/Blender/Assets'. */
127 library->dirpath, sizeof(library->dirpath), documents_path, N_("Blender"), N_("Assets"));
128}
129
132/* -------------------------------------------------------------------- */
139static size_t strncpy_py_module(char *dst, const char *src, const size_t dst_maxncpy)
140{
141 const size_t dst_len_max = dst_maxncpy - 1;
142 dst[0] = '\0';
143 size_t i_src = 0, i_dst = 0;
144 while (src[i_src] && (i_dst < dst_len_max)) {
145 const char c = src[i_src++];
146 const bool is_alpha = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
147 /* The first character must be `[a-zA-Z]`. */
148 if (i_dst == 0 && !is_alpha) {
149 continue;
150 }
151 const bool is_num = (is_alpha == false) && ((c >= '0' && c <= '9') || c == '_');
152 if (!(is_alpha || is_num)) {
153 continue;
154 }
155 dst[i_dst++] = c;
156 }
157 dst[i_dst] = '\0';
158 return i_dst;
159}
160
162 const char *name,
163 const char *module,
164 const char *custom_dirpath)
165{
167 BLI_addtail(&userdef->extension_repos, repo);
168
169 /* Set the unique ID-name. */
170 BKE_preferences_extension_repo_name_set(userdef, repo, name);
171
172 /* Set the unique module-name. */
174
175 /* Set the directory. */
176 STRNCPY(repo->custom_dirpath, custom_dirpath);
179
180 /* While not a strict rule, ignored paths that already exist, *
181 * pointing to the same path is going to logical problems with package-management. */
182 LISTBASE_FOREACH (const bUserExtensionRepo *, repo_iter, &userdef->extension_repos) {
183 if (repo == repo_iter) {
184 continue;
185 }
186 if (BLI_path_cmp(repo->custom_dirpath, repo_iter->custom_dirpath) == 0) {
187 repo->custom_dirpath[0] = '\0';
188 break;
189 }
190 }
191
192 return repo;
193}
194
199
201{
203 userdef, "extensions.blender.org", "blender_org", "");
204 /* The trailing slash on this URL is important, without it a redirect is used. */
205 STRNCPY(repo->remote_url, "https://extensions.blender.org/api/v1/extensions/");
206 /* Disable `blender.org` by default, the initial "Online Preferences" section gives
207 * the option to enable this. */
209 return repo;
210}
211
213{
215 userdef, "User Default", "user_default", "");
216 return repo;
217}
218
225
233
235 bUserExtensionRepo *repo,
236 const char *name)
237{
238 if (*name == '\0') {
239 name = "User Repository";
240 }
241 STRNCPY_UTF8(repo->name, name);
242
244 repo,
245 name,
246 '.',
248 sizeof(repo->name));
249}
250
252 bUserExtensionRepo *repo,
253 const char *module)
254{
255 if (strncpy_py_module(repo->module, module, sizeof(repo->module)) == 0) {
256 STRNCPY(repo->module, "repository");
257 }
258
260 repo,
261 module,
262 '_',
264 sizeof(repo->module));
265}
266
268{
269 /* NOTE: this should only ever return false in the case of corrupt file/memory
270 * and can be considered an exceptional situation. */
271 char module_test[sizeof(bUserExtensionRepo::module)];
272 const size_t module_len = strncpy_py_module(module_test, repo->module, sizeof(repo->module));
273 if (module_len == 0) {
274 return false;
275 }
276 if (module_len != BLI_strnlen(repo->module, sizeof(repo->module))) {
277 return false;
278 }
279 return true;
280}
281
283{
284 STRNCPY(repo->custom_dirpath, path);
285}
286
288 char *dirpath,
289 const int dirpath_maxncpy)
290{
292 return BLI_strncpy_rlen(dirpath, repo->custom_dirpath, dirpath_maxncpy);
293 }
294
295 std::optional<std::string> path = std::nullopt;
296
297 uint8_t source = repo->source;
300 }
301
302 switch (source) {
305 break;
306 }
307 default: { /* #USER_EXTENSION_REPO_SOURCE_USER. */
309 break;
310 }
311 }
312
313 /* Highly unlikely to fail as the directory doesn't have to exist. */
314 if (!path) {
315 dirpath[0] = '\0';
316 return 0;
317 }
318 return BLI_path_join(dirpath, dirpath_maxncpy, path.value().c_str(), repo->module);
319}
320
322 char *dirpath,
323 const int dirpath_maxncpy)
324{
325 if (std::optional<std::string> path = BKE_appdir_folder_id_user_notest(BLENDER_USER_EXTENSIONS,
326 nullptr))
327 {
328 return BLI_path_join(dirpath, dirpath_maxncpy, path.value().c_str(), ".user", repo->module);
329 }
330 return 0;
331}
332
334{
335 return static_cast<bUserExtensionRepo *>(BLI_findlink(&userdef->extension_repos, index));
336}
337
344
345static bool url_char_is_delimiter(const char ch)
346{
347 /* Punctuation (space to comma). */
348 if (ch >= 32 && ch <= 44) {
349 return true;
350 }
351 /* Other characters (colon to at-sign). */
352 if (ch >= 58 && ch <= 64) {
353 return true;
354 }
355 if (ELEM(ch, '/', '\\')) {
356 return true;
357 }
358 return false;
359}
360
362 const UserDef *userdef, const char *remote_url_full, const bool only_enabled)
363{
364 const int path_full_len = strlen(remote_url_full);
365 const int path_full_offset = BKE_preferences_extension_repo_remote_scheme_end(remote_url_full);
366
368 if (only_enabled && (repo->flag & USER_EXTENSION_REPO_FLAG_DISABLED)) {
369 continue;
370 }
371
372 /* Has a valid remote path to check. */
373 if ((repo->flag & USER_EXTENSION_REPO_FLAG_USE_REMOTE_URL) == 0) {
374 continue;
375 }
376 if (repo->remote_url[0] == '\0') {
377 continue;
378 }
379
380 /* Set path variables which may be offset by the "scheme". */
381 const char *path_repo = repo->remote_url;
382 const char *path_test = remote_url_full;
383 int path_test_len = path_full_len;
384
385 /* Allow paths beginning with both `http` & `https` to be considered equivalent.
386 * This is done by skipping the "scheme" prefix both have a scheme. */
387 if (path_full_offset) {
388 const int path_repo_offset = BKE_preferences_extension_repo_remote_scheme_end(path_repo);
389 if (path_repo_offset) {
390 path_repo += path_repo_offset;
391 path_test += path_full_offset;
392 path_test_len -= path_full_offset;
393 }
394 }
395
396 /* The length of the path without trailing slashes. */
397 int path_repo_len = strlen(path_repo);
398 while (path_repo_len && ELEM(path_repo[path_repo_len - 1], '/', '\\')) {
399 path_repo_len--;
400 }
401
402 if (path_test_len <= path_repo_len) {
403 continue;
404 }
405 if (memcmp(path_repo, path_test, path_repo_len) != 0) {
406 continue;
407 }
408
409 /* A delimiter must follow to ensure `path_test` doesn't reference a longer host-name.
410 * Will typically be a `/` or a `:`. */
411 if (!url_char_is_delimiter(path_test[path_repo_len])) {
412 continue;
413 }
414 return repo;
415 }
416 return nullptr;
417}
418
420{
421 /* Technically the "://" are not part of the scheme, so subtract 3 from the return value. */
422 const char *scheme_check[] = {
423 "http://",
424 "https://",
425 "file://",
426 };
427 for (int i = 0; i < ARRAY_SIZE(scheme_check); i++) {
428 const char *scheme = scheme_check[i];
429 int scheme_len = strlen(scheme);
430 if (strncmp(url, scheme, scheme_len) == 0) {
431 return scheme_len - 3;
432 }
433 }
434 return 0;
435}
436
438 char name[sizeof(bUserExtensionRepo::name)])
439{
440#ifdef _WIN32
441 const bool is_win32 = true;
442#else
443 const bool is_win32 = false;
444#endif
445 const bool is_file = STRPREFIX(remote_url, "file://");
446 name[0] = '\0';
447 if (int offset = BKE_preferences_extension_repo_remote_scheme_end(remote_url)) {
448 /* Skip the `://`. */
449 remote_url += (offset + 3);
450
451 if (is_win32) {
452 if (is_file) {
453 /* Skip the slash prefix for: `/C:/`,
454 * not *required* but seems like a bug if it's not done. */
455 if (remote_url[0] == '/' && isalpha(remote_url[1]) && (remote_url[2] == ':')) {
456 remote_url += 1;
457 }
458 }
459 }
460 }
461 if (UNLIKELY(remote_url[0] == '\0')) {
462 return;
463 }
464
465 const char *c = remote_url;
466 if (is_file) {
467 /* TODO: decode the URL, see: #GHOST_URL_decode which is not a public function. */
468
469 /* Don't use domain name only logic for file paths as this causes
470 * `file:///path/to/repo/index.json` -> `/path`
471 * In this case `/path/to/repo` is preferred. */
472 c = BLI_path_basename(remote_url);
473 /* Remove trailing slash. */
474 while ((remote_url < c) && url_char_is_delimiter(*(c - 1))) {
475 c--;
476 }
477 }
478 else {
479 /* Skip any delimiters (likely forward slashes for `file:///` on UNIX).
480 * Although the `file://` case is handled already. So this is quite unlikely.
481 * Skip them anyway because failing to do so may cause the domain to be an empty string. */
482 while (*c && url_char_is_delimiter(*c)) {
483 c++;
484 }
485 /* Skip the domain name. */
486 while (*c && !url_char_is_delimiter(*c)) {
487 c++;
488 }
489 }
490
492 name, remote_url, std::min(size_t(c - remote_url) + 1, sizeof(bUserExtensionRepo::name)));
493
494 if (is_win32) {
495 if (is_file) {
497 }
498 }
499}
500
502 const bUserExtensionRepo *repo)
503{
504 return BLI_findindex(&userdef->extension_repos, repo);
505}
506
508{
509 if (repo->access_token) {
510 BLO_read_string(reader, &repo->access_token);
511 }
512}
513
515{
516 if (repo->access_token) {
517 BLO_write_string(writer, repo->access_token);
518 }
519}
520
523/* -------------------------------------------------------------------- */
528 const char *shelf_idname)
529{
531 BLI_addtail(&userdef->asset_shelves_settings, settings);
532 STRNCPY(settings->shelf_idname, shelf_idname);
533 BLI_assert(BLI_listbase_is_empty(&settings->enabled_catalog_paths));
534 return settings;
535}
536
538 const char *shelf_idname)
539{
541 shelf_idname))
542 {
543 return settings;
544 }
545 return asset_shelf_settings_new(userdef, shelf_idname);
546}
547
549 const char *shelf_idname)
550{
551 return static_cast<bUserAssetShelfSettings *>(
553 shelf_idname,
554 offsetof(bUserAssetShelfSettings, shelf_idname)));
555}
556
558 const char *shelf_idname,
559 const char *catalog_path)
560{
562 shelf_idname);
563 if (!settings) {
564 return false;
565 }
566 return BKE_asset_catalog_path_list_has_path(settings->enabled_catalog_paths, catalog_path);
567}
568
570 const char *shelf_idname,
571 const char *catalog_path)
572{
574 userdef, shelf_idname, catalog_path))
575 {
576 return false;
577 }
578
579 bUserAssetShelfSettings *settings = asset_shelf_settings_ensure(userdef, shelf_idname);
580 BKE_asset_catalog_path_list_add_path(settings->enabled_catalog_paths, catalog_path);
581 return true;
582}
583
std::optional< std::string > BKE_appdir_folder_id_user_notest(int folder_id, const char *subfolder) ATTR_WARN_UNUSED_RESULT
Definition appdir.cc:713
bool BKE_appdir_folder_documents(char *dir) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
Definition appdir.cc:196
@ BLENDER_USER_EXTENSIONS
@ BLENDER_SYSTEM_EXTENSIONS
std::optional< std::string > BKE_appdir_folder_id(int folder_id, const char *subfolder) ATTR_WARN_UNUSED_RESULT
Definition appdir.cc:704
bool BKE_asset_catalog_path_list_has_path(const ListBase &catalog_path_list, const char *catalog_path)
void BKE_asset_catalog_path_list_add_path(ListBase &catalog_path_list, const char *catalog_path)
#define BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME
#define BLI_assert(a)
Definition BLI_assert.h:50
File and directory operations.
bool BLI_is_file(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:438
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
void * BLI_findstring(const struct ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
#define LISTBASE_FOREACH(type, var, list)
void BLI_freelinkN(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:269
void * BLI_findlink(const struct ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
int BLI_findindex(const struct ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
bool BLI_path_parent_dir(char *path) ATTR_NONNULL(1)
void void void const char * BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
void BLI_path_slash_native(char *path) ATTR_NONNULL(1)
int BLI_path_normalize(char *path) ATTR_NONNULL(1)
bool BLI_path_contains(const char *container_path, const char *containee_path) ATTR_NONNULL(1
#define BLI_path_join(...)
void BLI_path_slash_rstrip(char *path) ATTR_NONNULL(1)
#define FILE_MAXDIR
#define BLI_path_cmp
#define STRNCPY(dst, src)
Definition BLI_string.h:593
int char char int int int int size_t BLI_strnlen(const char *str, size_t maxlen) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition string.c:909
char char size_t BLI_strncpy_rlen(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
char * BLI_strncpy_utf8(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define STRNCPY_UTF8(dst, src)
void BLI_uniquename(const struct ListBase *list, void *vlink, const char *defname, char delim, int name_offset, size_t name_maxncpy) ATTR_NONNULL(1
#define STRPREFIX(a, b)
#define ARRAY_SIZE(arr)
#define UNLIKELY(x)
#define ELEM(...)
void BLO_read_string(BlendDataReader *reader, char **ptr_p)
Definition readfile.cc:4992
void BLO_write_string(BlendWriter *writer, const char *data_ptr)
#define DATA_(msgid)
#define DNA_struct_default_alloc(struct_name)
@ USER_EXTENSION_REPO_SOURCE_SYSTEM
@ USER_EXTENSION_REPO_SOURCE_USER
@ USER_EXTENSION_REPO_FLAG_DISABLED
@ USER_EXTENSION_REPO_FLAG_USE_CUSTOM_DIRECTORY
@ USER_EXTENSION_REPO_FLAG_SYNC_ON_STARTUP
@ USER_EXTENSION_REPO_FLAG_USE_REMOTE_URL
Read Guarded memory(de)allocation.
#define offsetof(t, d)
void BKE_preferences_extension_remote_to_name(const char *remote_url, char name[sizeof(bUserExtensionRepo::name)])
bUserExtensionRepo * BKE_preferences_extension_repo_find_by_remote_url_prefix(const UserDef *userdef, const char *remote_url_full, const bool only_enabled)
bool BKE_preferences_extension_repo_module_is_valid(const bUserExtensionRepo *repo)
bUserAssetLibrary * BKE_preferences_asset_library_find_by_name(const UserDef *userdef, const char *name)
bUserExtensionRepo * BKE_preferences_extension_repo_find_by_module(const UserDef *userdef, const char *module)
void BKE_preferences_asset_library_default_add(UserDef *userdef)
size_t BKE_preferences_extension_repo_user_dirpath_get(const bUserExtensionRepo *repo, char *dirpath, const int dirpath_maxncpy)
void BKE_preferences_extension_repo_write_data(BlendWriter *writer, const bUserExtensionRepo *repo)
void BKE_preferences_extension_repo_module_set(UserDef *userdef, bUserExtensionRepo *repo, const char *module)
static size_t strncpy_py_module(char *dst, const char *src, const size_t dst_maxncpy)
int BKE_preferences_extension_repo_remote_scheme_end(const char *url)
bool BKE_preferences_asset_shelf_settings_is_catalog_path_enabled(const UserDef *userdef, const char *shelf_idname, const char *catalog_path)
void BKE_preferences_asset_library_path_set(bUserAssetLibrary *library, const char *path)
bUserAssetLibrary * BKE_preferences_asset_library_find_index(const UserDef *userdef, int index)
bUserExtensionRepo * BKE_preferences_extension_repo_add(UserDef *userdef, const char *name, const char *module, const char *custom_dirpath)
bUserExtensionRepo * BKE_preferences_extension_repo_find_index(const UserDef *userdef, int index)
size_t BKE_preferences_extension_repo_dirpath_get(const bUserExtensionRepo *repo, char *dirpath, const int dirpath_maxncpy)
static bool url_char_is_delimiter(const char ch)
bUserAssetShelfSettings * BKE_preferences_asset_shelf_settings_get(const UserDef *userdef, const char *shelf_idname)
int BKE_preferences_extension_repo_get_index(const UserDef *userdef, const bUserExtensionRepo *repo)
void BKE_preferences_extension_repo_remove(UserDef *userdef, bUserExtensionRepo *repo)
int BKE_preferences_asset_library_get_index(const UserDef *userdef, const bUserAssetLibrary *library)
void BKE_preferences_extension_repo_add_defaults_all(UserDef *userdef)
bUserAssetLibrary * BKE_preferences_asset_library_containing_path(const UserDef *userdef, const char *path)
bUserExtensionRepo * BKE_preferences_extension_repo_add_default_user(UserDef *userdef)
bUserAssetLibrary * BKE_preferences_asset_library_add(UserDef *userdef, const char *name, const char *dirpath)
bUserExtensionRepo * BKE_preferences_extension_repo_add_default_system(UserDef *userdef)
void BKE_preferences_extension_repo_read_data(BlendDataReader *reader, bUserExtensionRepo *repo)
void BKE_preferences_asset_library_name_set(UserDef *userdef, bUserAssetLibrary *library, const char *name)
void BKE_preferences_extension_repo_name_set(UserDef *userdef, bUserExtensionRepo *repo, const char *name)
bUserExtensionRepo * BKE_preferences_extension_repo_add_default_remote(UserDef *userdef)
void BKE_preferences_asset_library_remove(UserDef *userdef, bUserAssetLibrary *library)
bool BKE_preferences_asset_shelf_settings_ensure_catalog_path_enabled(UserDef *userdef, const char *shelf_idname, const char *catalog_path)
static bUserAssetShelfSettings * asset_shelf_settings_ensure(UserDef *userdef, const char *shelf_idname)
static bUserAssetShelfSettings * asset_shelf_settings_new(UserDef *userdef, const char *shelf_idname)
void BKE_preferences_extension_repo_custom_dirpath_set(bUserExtensionRepo *repo, const char *path)
static struct PyModuleDef module
Definition python.cpp:991
unsigned char uint8_t
Definition stdint.h:78
struct ListBase asset_shelves_settings
struct ListBase extension_repos
struct ListBase asset_libraries
#define N_(msgid)