Blender V5.0
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
10
11#include <cstring>
12
13#include "BLI_fileops.h"
14#include "BLI_listbase.h"
15#include "BLI_path_utils.hh"
16#include "BLI_string.h"
17#include "BLI_string_utf8.h"
18#include "BLI_string_utils.hh"
19
20#include "BKE_appdir.hh"
21#include "BKE_asset.hh"
22#include "BKE_preferences.h"
23
24#include "BLT_translation.hh"
25
26#include "BLO_read_write.hh"
27
28#include "DNA_defaults.h"
29#include "DNA_userdef_types.h"
30
31#define U BLI_STATIC_ASSERT(false, "Global 'U' not allowed, only use arguments passed in!")
32
33/* -------------------------------------------------------------------- */
36
38
39bool exists()
40{
41 const std::optional<std::string> cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, nullptr);
42 if (!cfgdir.has_value()) {
43 return false;
44 }
45
46 char userpref[FILE_MAX];
47 BLI_path_join(userpref, sizeof(userpref), cfgdir->c_str(), BLENDER_USERPREF_FILE);
48 return BLI_exists(userpref);
49}
50
51} // namespace blender::bke::preferences
52
54
55/* -------------------------------------------------------------------- */
58
60 const char *name,
61 const char *dirpath)
62{
64
65 BLI_addtail(&userdef->asset_libraries, library);
68 }
69 if (name) {
71 }
72 if (dirpath) {
73 STRNCPY(library->dirpath, dirpath);
74 }
75
76 return library;
77}
78
80{
81 BLI_freelinkN(&userdef->asset_libraries, library);
82}
83
85 bUserAssetLibrary *library,
86 const char *name)
87{
88 STRNCPY_UTF8(library->name, name);
90 library,
91 name,
92 '.',
94 sizeof(library->name));
95}
96
98{
99 STRNCPY(library->dirpath, path);
100 if (BLI_is_file(library->dirpath)) {
102 }
103}
104
106{
107 return static_cast<bUserAssetLibrary *>(BLI_findlink(&userdef->asset_libraries, index));
108}
109
111 const char *name)
112{
113 return static_cast<bUserAssetLibrary *>(
115}
116
118 const char *path)
119{
120 LISTBASE_FOREACH (bUserAssetLibrary *, asset_lib_pref, &userdef->asset_libraries) {
121 if (asset_lib_pref->dirpath[0] && BLI_path_contains(asset_lib_pref->dirpath, path)) {
122 return asset_lib_pref;
123 }
124 }
125 return nullptr;
126}
127
129 const bUserAssetLibrary *library)
130{
131 return BLI_findindex(&userdef->asset_libraries, library);
132}
133
135{
136 char documents_path[FILE_MAXDIR];
137
138 /* No home or documents path found, not much we can do. */
139 if (!BKE_appdir_folder_documents(documents_path) || !documents_path[0]) {
140 return;
141 }
142
145
146 /* Add new "Default" library under '[doc_path]/Blender/Assets'. */
148 library->dirpath, sizeof(library->dirpath), documents_path, N_("Blender"), N_("Assets"));
149}
150
152
153/* -------------------------------------------------------------------- */
156
160static size_t strncpy_py_module(char *dst, const char *src, const size_t dst_maxncpy)
161{
162 const size_t dst_len_max = dst_maxncpy - 1;
163 dst[0] = '\0';
164 size_t i_src = 0, i_dst = 0;
165 while (src[i_src] && (i_dst < dst_len_max)) {
166 const char c = src[i_src++];
167 const bool is_alpha = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
168 /* The first character must be `[a-zA-Z]`. */
169 if (i_dst == 0 && !is_alpha) {
170 continue;
171 }
172 const bool is_num = (is_alpha == false) && ((c >= '0' && c <= '9') || c == '_');
173 if (!(is_alpha || is_num)) {
174 continue;
175 }
176 dst[i_dst++] = c;
177 }
178 dst[i_dst] = '\0';
179 return i_dst;
180}
181
183 const char *name,
184 const char *module,
185 const char *custom_dirpath)
186{
188 BLI_addtail(&userdef->extension_repos, repo);
189
190 /* Set the unique ID-name. */
192
193 /* Set the unique module-name. */
195
196 /* Set the directory. */
197 STRNCPY(repo->custom_dirpath, custom_dirpath);
200
201 /* While not a strict rule, ignored paths that already exist, *
202 * pointing to the same path is going to logical problems with package-management. */
203 LISTBASE_FOREACH (const bUserExtensionRepo *, repo_iter, &userdef->extension_repos) {
204 if (repo == repo_iter) {
205 continue;
206 }
207 if (BLI_path_cmp(repo->custom_dirpath, repo_iter->custom_dirpath) == 0) {
208 repo->custom_dirpath[0] = '\0';
209 break;
210 }
211 }
212
213 return repo;
214}
215
220
222{
224 userdef, "extensions.blender.org", "blender_org", "");
225 /* The trailing slash on this URL is important, without it a redirect is used. */
226 STRNCPY(repo->remote_url, "https://extensions.blender.org/api/v1/extensions/");
227 /* Disable `blender.org` by default, the initial "Online Preferences" section gives
228 * the option to enable this. */
230 return repo;
231}
232
234{
236 userdef, "User Default", "user_default", "");
237 return repo;
238}
239
246
254
256 bUserExtensionRepo *repo,
257 const char *name)
258{
259 if (*name == '\0') {
260 name = "User Repository";
261 }
262 STRNCPY_UTF8(repo->name, name);
263
265 repo,
266 name,
267 '.',
269 sizeof(repo->name));
270}
271
273 bUserExtensionRepo *repo,
274 const char *module)
275{
276 if (strncpy_py_module(repo->module, module, sizeof(repo->module)) == 0) {
277 STRNCPY(repo->module, "repository");
278 }
279
281 repo,
282 module,
283 '_',
285 sizeof(repo->module));
286}
287
289{
290 /* NOTE: this should only ever return false in the case of corrupt file/memory
291 * and can be considered an exceptional situation. */
292 char module_test[sizeof(bUserExtensionRepo::module)];
293 const size_t module_len = strncpy_py_module(module_test, repo->module, sizeof(repo->module));
294 if (module_len == 0) {
295 return false;
296 }
297 if (module_len != STRNLEN(repo->module)) {
298 return false;
299 }
300 return true;
301}
302
304{
305 STRNCPY(repo->custom_dirpath, path);
306}
307
309 char *dirpath,
310 const int dirpath_maxncpy)
311{
313 return BLI_strncpy_rlen(dirpath, repo->custom_dirpath, dirpath_maxncpy);
314 }
315
316 std::optional<std::string> path = std::nullopt;
317
318 uint8_t source = repo->source;
321 }
322
323 switch (source) {
326 break;
327 }
328 default: { /* #USER_EXTENSION_REPO_SOURCE_USER. */
330 break;
331 }
332 }
333
334 /* Highly unlikely to fail as the directory doesn't have to exist. */
335 if (!path) {
336 dirpath[0] = '\0';
337 return 0;
338 }
339 return BLI_path_join(dirpath, dirpath_maxncpy, path.value().c_str(), repo->module);
340}
341
343 char *dirpath,
344 const int dirpath_maxncpy)
345{
346 if (std::optional<std::string> path = BKE_appdir_folder_id_user_notest(BLENDER_USER_EXTENSIONS,
347 nullptr))
348 {
349 return BLI_path_join(dirpath, dirpath_maxncpy, path.value().c_str(), ".user", repo->module);
350 }
351 return 0;
352}
353
355{
356 return static_cast<bUserExtensionRepo *>(BLI_findlink(&userdef->extension_repos, index));
357}
358
365
366static bool url_char_is_delimiter(const char ch)
367{
368 /* Punctuation (space to comma). */
369 if (ch >= 32 && ch <= 44) {
370 return true;
371 }
372 /* Other characters (colon to at-sign). */
373 if (ch >= 58 && ch <= 64) {
374 return true;
375 }
376 if (ELEM(ch, '/', '\\')) {
377 return true;
378 }
379 return false;
380}
381
383 const UserDef *userdef, const char *remote_url_full, const bool only_enabled)
384{
385 const int path_full_len = strlen(remote_url_full);
386 const int path_full_offset = BKE_preferences_extension_repo_remote_scheme_end(remote_url_full);
387
389 if (only_enabled && (repo->flag & USER_EXTENSION_REPO_FLAG_DISABLED)) {
390 continue;
391 }
392
393 /* Has a valid remote path to check. */
394 if ((repo->flag & USER_EXTENSION_REPO_FLAG_USE_REMOTE_URL) == 0) {
395 continue;
396 }
397 if (repo->remote_url[0] == '\0') {
398 continue;
399 }
400
401 /* Set path variables which may be offset by the "scheme". */
402 const char *path_repo = repo->remote_url;
403 const char *path_test = remote_url_full;
404 int path_test_len = path_full_len;
405
406 /* Allow paths beginning with both `http` & `https` to be considered equivalent.
407 * This is done by skipping the "scheme" prefix both have a scheme. */
408 if (path_full_offset) {
409 const int path_repo_offset = BKE_preferences_extension_repo_remote_scheme_end(path_repo);
410 if (path_repo_offset) {
411 path_repo += path_repo_offset;
412 path_test += path_full_offset;
413 path_test_len -= path_full_offset;
414 }
415 }
416
417 /* The length of the path without trailing slashes. */
418 int path_repo_len = strlen(path_repo);
419 while (path_repo_len && ELEM(path_repo[path_repo_len - 1], '/', '\\')) {
420 path_repo_len--;
421 }
422
423 if (path_test_len <= path_repo_len) {
424 continue;
425 }
426 if (memcmp(path_repo, path_test, path_repo_len) != 0) {
427 continue;
428 }
429
430 /* A delimiter must follow to ensure `path_test` doesn't reference a longer host-name.
431 * Will typically be a `/` or a `:`. */
432 if (!url_char_is_delimiter(path_test[path_repo_len])) {
433 continue;
434 }
435 return repo;
436 }
437 return nullptr;
438}
439
441{
442 /* Technically the "://" are not part of the scheme, so subtract 3 from the return value. */
443 const char *scheme_check[] = {
444 "http://",
445 "https://",
446 "file://",
447 };
448 for (int i = 0; i < ARRAY_SIZE(scheme_check); i++) {
449 const char *scheme = scheme_check[i];
450 int scheme_len = strlen(scheme);
451 if (strncmp(url, scheme, scheme_len) == 0) {
452 return scheme_len - 3;
453 }
454 }
455 return 0;
456}
457
459 char name[sizeof(bUserExtensionRepo::name)])
460{
461#ifdef _WIN32
462 const bool is_win32 = true;
463#else
464 const bool is_win32 = false;
465#endif
466 const bool is_file = STRPREFIX(remote_url, "file://");
467 name[0] = '\0';
468 if (int offset = BKE_preferences_extension_repo_remote_scheme_end(remote_url)) {
469 /* Skip the `://`. */
470 remote_url += (offset + 3);
471
472 if (is_file) {
473 if (is_win32) {
474 /* Skip the slash prefix for: `/C:/`,
475 * not *required* but seems like a bug if it's not done. */
476 if (remote_url[0] == '/' && isalpha(remote_url[1]) && (remote_url[2] == ':')) {
477 remote_url += 1;
478 }
479 }
480 }
481 else {
482 /* Skip the `www` as it's not useful information. */
483 if (BLI_str_startswith(remote_url, "www.")) {
484 remote_url += 4;
485 }
486 }
487 }
488 if (UNLIKELY(remote_url[0] == '\0')) {
489 return;
490 }
491
492 const char *c = remote_url;
493 if (is_file) {
494 /* TODO: decode the URL, see: #GHOST_URL_decode which is not a public function. */
495
496 /* Don't use domain name only logic for file paths as this causes
497 * `file:///path/to/repo/index.json` -> `/path`
498 * In this case `/path/to/repo` is preferred. */
499 c = BLI_path_basename(remote_url);
500 /* Remove trailing slash. */
501 while ((remote_url < c) && url_char_is_delimiter(*(c - 1))) {
502 c--;
503 }
504 }
505 else {
506 /* Skip any delimiters (likely forward slashes for `file:///` on UNIX).
507 * Although the `file://` case is handled already. So this is quite unlikely.
508 * Skip them anyway because failing to do so may cause the domain to be an empty string. */
509 while (*c && url_char_is_delimiter(*c)) {
510 c++;
511 }
512 /* Skip the domain name. */
513 while (*c && !url_char_is_delimiter(*c)) {
514 c++;
515 }
516 }
517
519 name, remote_url, std::min(size_t(c - remote_url) + 1, sizeof(bUserExtensionRepo::name)));
520
521 if (is_win32) {
522 if (is_file) {
524 }
525 }
526}
527
529 const bUserExtensionRepo *repo)
530{
531 return BLI_findindex(&userdef->extension_repos, repo);
532}
533
535{
536 if (repo->access_token) {
537 BLO_read_string(reader, &repo->access_token);
538 }
539}
540
542{
543 if (repo->access_token) {
544 BLO_write_string(writer, repo->access_token);
545 }
546}
547
549
550/* -------------------------------------------------------------------- */
553
555 const char *shelf_idname)
556{
558 BLI_addtail(&userdef->asset_shelves_settings, settings);
559 STRNCPY(settings->shelf_idname, shelf_idname);
561 return settings;
562}
563
565 const char *shelf_idname)
566{
568 shelf_idname))
569 {
570 return settings;
571 }
572 return asset_shelf_settings_new(userdef, shelf_idname);
573}
574
576 const char *shelf_idname)
577{
578 return static_cast<bUserAssetShelfSettings *>(
580 shelf_idname,
581 offsetof(bUserAssetShelfSettings, shelf_idname)));
582}
583
585 const char *shelf_idname,
586 const char *catalog_path)
587{
589 shelf_idname);
590 if (!settings) {
591 return false;
592 }
593 return BKE_asset_catalog_path_list_has_path(settings->enabled_catalog_paths, catalog_path);
594}
595
597 const char *shelf_idname,
598 const char *catalog_path)
599{
601 userdef, shelf_idname, catalog_path))
602 {
603 return false;
604 }
605
606 bUserAssetShelfSettings *settings = asset_shelf_settings_ensure(userdef, shelf_idname);
608 return true;
609}
610
std::optional< std::string > BKE_appdir_folder_id_user_notest(int folder_id, const char *subfolder) ATTR_WARN_UNUSED_RESULT
Definition appdir.cc:730
#define BLENDER_USERPREF_FILE
bool BKE_appdir_folder_documents(char *dir) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
Definition appdir.cc:172
std::optional< std::string > BKE_appdir_folder_id(int folder_id, const char *subfolder) ATTR_WARN_UNUSED_RESULT
Definition appdir.cc:721
@ BLENDER_USER_EXTENSIONS
@ BLENDER_SYSTEM_EXTENSIONS
@ BLENDER_USER_CONFIG
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:46
File and directory operations.
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:360
bool BLI_is_file(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:448
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
void * BLI_findlink(const ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:534
#define LISTBASE_FOREACH(type, var, list)
void * BLI_findstring(const ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:608
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
void BLI_freelinkN(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:270
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
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
#define FILE_MAX
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
int bool BLI_str_startswith(const char *__restrict str, const char *__restrict start) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
#define STRNLEN(str)
Definition BLI_string.h:613
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
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:5828
void BLO_write_string(BlendWriter *writer, const char *data_ptr)
#define DATA_(msgid)
@ ASSET_IMPORT_APPEND_REUSE
#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
#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:796
const char * name
UserDef_Experimental experimental
struct ListBase asset_shelves_settings
struct ListBase extension_repos
struct ListBase asset_libraries
i
Definition text_draw.cc:230
#define N_(msgid)