Blender V4.5
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);
66
67 if (name) {
68 BKE_preferences_asset_library_name_set(userdef, library, name);
69 }
70 if (dirpath) {
71 STRNCPY(library->dirpath, dirpath);
72 }
73
74 return library;
75}
76
78{
79 BLI_freelinkN(&userdef->asset_libraries, library);
80}
81
83 bUserAssetLibrary *library,
84 const char *name)
85{
86 STRNCPY_UTF8(library->name, name);
88 library,
89 name,
90 '.',
92 sizeof(library->name));
93}
94
96{
97 STRNCPY(library->dirpath, path);
98 if (BLI_is_file(library->dirpath)) {
100 }
101}
102
104{
105 return static_cast<bUserAssetLibrary *>(BLI_findlink(&userdef->asset_libraries, index));
106}
107
109 const char *name)
110{
111 return static_cast<bUserAssetLibrary *>(
113}
114
116 const char *path)
117{
118 LISTBASE_FOREACH (bUserAssetLibrary *, asset_lib_pref, &userdef->asset_libraries) {
119 if (asset_lib_pref->dirpath[0] && BLI_path_contains(asset_lib_pref->dirpath, path)) {
120 return asset_lib_pref;
121 }
122 }
123 return nullptr;
124}
125
127 const bUserAssetLibrary *library)
128{
129 return BLI_findindex(&userdef->asset_libraries, library);
130}
131
133{
134 char documents_path[FILE_MAXDIR];
135
136 /* No home or documents path found, not much we can do. */
137 if (!BKE_appdir_folder_documents(documents_path) || !documents_path[0]) {
138 return;
139 }
140
143
144 /* Add new "Default" library under '[doc_path]/Blender/Assets'. */
146 library->dirpath, sizeof(library->dirpath), documents_path, N_("Blender"), N_("Assets"));
147}
148
150
151/* -------------------------------------------------------------------- */
154
158static size_t strncpy_py_module(char *dst, const char *src, const size_t dst_maxncpy)
159{
160 const size_t dst_len_max = dst_maxncpy - 1;
161 dst[0] = '\0';
162 size_t i_src = 0, i_dst = 0;
163 while (src[i_src] && (i_dst < dst_len_max)) {
164 const char c = src[i_src++];
165 const bool is_alpha = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
166 /* The first character must be `[a-zA-Z]`. */
167 if (i_dst == 0 && !is_alpha) {
168 continue;
169 }
170 const bool is_num = (is_alpha == false) && ((c >= '0' && c <= '9') || c == '_');
171 if (!(is_alpha || is_num)) {
172 continue;
173 }
174 dst[i_dst++] = c;
175 }
176 dst[i_dst] = '\0';
177 return i_dst;
178}
179
181 const char *name,
182 const char *module,
183 const char *custom_dirpath)
184{
186 BLI_addtail(&userdef->extension_repos, repo);
187
188 /* Set the unique ID-name. */
189 BKE_preferences_extension_repo_name_set(userdef, repo, name);
190
191 /* Set the unique module-name. */
193
194 /* Set the directory. */
195 STRNCPY(repo->custom_dirpath, custom_dirpath);
198
199 /* While not a strict rule, ignored paths that already exist, *
200 * pointing to the same path is going to logical problems with package-management. */
201 LISTBASE_FOREACH (const bUserExtensionRepo *, repo_iter, &userdef->extension_repos) {
202 if (repo == repo_iter) {
203 continue;
204 }
205 if (BLI_path_cmp(repo->custom_dirpath, repo_iter->custom_dirpath) == 0) {
206 repo->custom_dirpath[0] = '\0';
207 break;
208 }
209 }
210
211 return repo;
212}
213
218
220{
222 userdef, "extensions.blender.org", "blender_org", "");
223 /* The trailing slash on this URL is important, without it a redirect is used. */
224 STRNCPY(repo->remote_url, "https://extensions.blender.org/api/v1/extensions/");
225 /* Disable `blender.org` by default, the initial "Online Preferences" section gives
226 * the option to enable this. */
228 return repo;
229}
230
232{
234 userdef, "User Default", "user_default", "");
235 return repo;
236}
237
244
252
254 bUserExtensionRepo *repo,
255 const char *name)
256{
257 if (*name == '\0') {
258 name = "User Repository";
259 }
260 STRNCPY_UTF8(repo->name, name);
261
263 repo,
264 name,
265 '.',
267 sizeof(repo->name));
268}
269
271 bUserExtensionRepo *repo,
272 const char *module)
273{
274 if (strncpy_py_module(repo->module, module, sizeof(repo->module)) == 0) {
275 STRNCPY(repo->module, "repository");
276 }
277
279 repo,
280 module,
281 '_',
283 sizeof(repo->module));
284}
285
287{
288 /* NOTE: this should only ever return false in the case of corrupt file/memory
289 * and can be considered an exceptional situation. */
290 char module_test[sizeof(bUserExtensionRepo::module)];
291 const size_t module_len = strncpy_py_module(module_test, repo->module, sizeof(repo->module));
292 if (module_len == 0) {
293 return false;
294 }
295 if (module_len != STRNLEN(repo->module)) {
296 return false;
297 }
298 return true;
299}
300
302{
303 STRNCPY(repo->custom_dirpath, path);
304}
305
307 char *dirpath,
308 const int dirpath_maxncpy)
309{
311 return BLI_strncpy_rlen(dirpath, repo->custom_dirpath, dirpath_maxncpy);
312 }
313
314 std::optional<std::string> path = std::nullopt;
315
316 uint8_t source = repo->source;
319 }
320
321 switch (source) {
324 break;
325 }
326 default: { /* #USER_EXTENSION_REPO_SOURCE_USER. */
328 break;
329 }
330 }
331
332 /* Highly unlikely to fail as the directory doesn't have to exist. */
333 if (!path) {
334 dirpath[0] = '\0';
335 return 0;
336 }
337 return BLI_path_join(dirpath, dirpath_maxncpy, path.value().c_str(), repo->module);
338}
339
341 char *dirpath,
342 const int dirpath_maxncpy)
343{
344 if (std::optional<std::string> path = BKE_appdir_folder_id_user_notest(BLENDER_USER_EXTENSIONS,
345 nullptr))
346 {
347 return BLI_path_join(dirpath, dirpath_maxncpy, path.value().c_str(), ".user", repo->module);
348 }
349 return 0;
350}
351
353{
354 return static_cast<bUserExtensionRepo *>(BLI_findlink(&userdef->extension_repos, index));
355}
356
363
364static bool url_char_is_delimiter(const char ch)
365{
366 /* Punctuation (space to comma). */
367 if (ch >= 32 && ch <= 44) {
368 return true;
369 }
370 /* Other characters (colon to at-sign). */
371 if (ch >= 58 && ch <= 64) {
372 return true;
373 }
374 if (ELEM(ch, '/', '\\')) {
375 return true;
376 }
377 return false;
378}
379
381 const UserDef *userdef, const char *remote_url_full, const bool only_enabled)
382{
383 const int path_full_len = strlen(remote_url_full);
384 const int path_full_offset = BKE_preferences_extension_repo_remote_scheme_end(remote_url_full);
385
387 if (only_enabled && (repo->flag & USER_EXTENSION_REPO_FLAG_DISABLED)) {
388 continue;
389 }
390
391 /* Has a valid remote path to check. */
392 if ((repo->flag & USER_EXTENSION_REPO_FLAG_USE_REMOTE_URL) == 0) {
393 continue;
394 }
395 if (repo->remote_url[0] == '\0') {
396 continue;
397 }
398
399 /* Set path variables which may be offset by the "scheme". */
400 const char *path_repo = repo->remote_url;
401 const char *path_test = remote_url_full;
402 int path_test_len = path_full_len;
403
404 /* Allow paths beginning with both `http` & `https` to be considered equivalent.
405 * This is done by skipping the "scheme" prefix both have a scheme. */
406 if (path_full_offset) {
407 const int path_repo_offset = BKE_preferences_extension_repo_remote_scheme_end(path_repo);
408 if (path_repo_offset) {
409 path_repo += path_repo_offset;
410 path_test += path_full_offset;
411 path_test_len -= path_full_offset;
412 }
413 }
414
415 /* The length of the path without trailing slashes. */
416 int path_repo_len = strlen(path_repo);
417 while (path_repo_len && ELEM(path_repo[path_repo_len - 1], '/', '\\')) {
418 path_repo_len--;
419 }
420
421 if (path_test_len <= path_repo_len) {
422 continue;
423 }
424 if (memcmp(path_repo, path_test, path_repo_len) != 0) {
425 continue;
426 }
427
428 /* A delimiter must follow to ensure `path_test` doesn't reference a longer host-name.
429 * Will typically be a `/` or a `:`. */
430 if (!url_char_is_delimiter(path_test[path_repo_len])) {
431 continue;
432 }
433 return repo;
434 }
435 return nullptr;
436}
437
439{
440 /* Technically the "://" are not part of the scheme, so subtract 3 from the return value. */
441 const char *scheme_check[] = {
442 "http://",
443 "https://",
444 "file://",
445 };
446 for (int i = 0; i < ARRAY_SIZE(scheme_check); i++) {
447 const char *scheme = scheme_check[i];
448 int scheme_len = strlen(scheme);
449 if (strncmp(url, scheme, scheme_len) == 0) {
450 return scheme_len - 3;
451 }
452 }
453 return 0;
454}
455
457 char name[sizeof(bUserExtensionRepo::name)])
458{
459#ifdef _WIN32
460 const bool is_win32 = true;
461#else
462 const bool is_win32 = false;
463#endif
464 const bool is_file = STRPREFIX(remote_url, "file://");
465 name[0] = '\0';
466 if (int offset = BKE_preferences_extension_repo_remote_scheme_end(remote_url)) {
467 /* Skip the `://`. */
468 remote_url += (offset + 3);
469
470 if (is_file) {
471 if (is_win32) {
472 /* Skip the slash prefix for: `/C:/`,
473 * not *required* but seems like a bug if it's not done. */
474 if (remote_url[0] == '/' && isalpha(remote_url[1]) && (remote_url[2] == ':')) {
475 remote_url += 1;
476 }
477 }
478 }
479 else {
480 /* Skip the `www` as it's not useful information. */
481 if (BLI_str_startswith(remote_url, "www.")) {
482 remote_url += 4;
483 }
484 }
485 }
486 if (UNLIKELY(remote_url[0] == '\0')) {
487 return;
488 }
489
490 const char *c = remote_url;
491 if (is_file) {
492 /* TODO: decode the URL, see: #GHOST_URL_decode which is not a public function. */
493
494 /* Don't use domain name only logic for file paths as this causes
495 * `file:///path/to/repo/index.json` -> `/path`
496 * In this case `/path/to/repo` is preferred. */
497 c = BLI_path_basename(remote_url);
498 /* Remove trailing slash. */
499 while ((remote_url < c) && url_char_is_delimiter(*(c - 1))) {
500 c--;
501 }
502 }
503 else {
504 /* Skip any delimiters (likely forward slashes for `file:///` on UNIX).
505 * Although the `file://` case is handled already. So this is quite unlikely.
506 * Skip them anyway because failing to do so may cause the domain to be an empty string. */
507 while (*c && url_char_is_delimiter(*c)) {
508 c++;
509 }
510 /* Skip the domain name. */
511 while (*c && !url_char_is_delimiter(*c)) {
512 c++;
513 }
514 }
515
517 name, remote_url, std::min(size_t(c - remote_url) + 1, sizeof(bUserExtensionRepo::name)));
518
519 if (is_win32) {
520 if (is_file) {
522 }
523 }
524}
525
527 const bUserExtensionRepo *repo)
528{
529 return BLI_findindex(&userdef->extension_repos, repo);
530}
531
533{
534 if (repo->access_token) {
535 BLO_read_string(reader, &repo->access_token);
536 }
537}
538
540{
541 if (repo->access_token) {
542 BLO_write_string(writer, repo->access_token);
543 }
544}
545
547
548/* -------------------------------------------------------------------- */
551
553 const char *shelf_idname)
554{
556 BLI_addtail(&userdef->asset_shelves_settings, settings);
557 STRNCPY(settings->shelf_idname, shelf_idname);
559 return settings;
560}
561
563 const char *shelf_idname)
564{
566 shelf_idname))
567 {
568 return settings;
569 }
570 return asset_shelf_settings_new(userdef, shelf_idname);
571}
572
574 const char *shelf_idname)
575{
576 return static_cast<bUserAssetShelfSettings *>(
578 shelf_idname,
579 offsetof(bUserAssetShelfSettings, shelf_idname)));
580}
581
583 const char *shelf_idname,
584 const char *catalog_path)
585{
587 shelf_idname);
588 if (!settings) {
589 return false;
590 }
591 return BKE_asset_catalog_path_list_has_path(settings->enabled_catalog_paths, catalog_path);
592}
593
595 const char *shelf_idname,
596 const char *catalog_path)
597{
599 userdef, shelf_idname, catalog_path))
600 {
601 return false;
602 }
603
604 bUserAssetShelfSettings *settings = asset_shelf_settings_ensure(userdef, shelf_idname);
606 return true;
607}
608
std::optional< std::string > BKE_appdir_folder_id_user_notest(int folder_id, const char *subfolder) ATTR_WARN_UNUSED_RESULT
Definition appdir.cc:726
#define BLENDER_USERPREF_FILE
@ BLENDER_USER_EXTENSIONS
@ BLENDER_SYSTEM_EXTENSIONS
@ BLENDER_USER_CONFIG
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:717
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:373
bool BLI_is_file(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:461
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:608
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
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:5351
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
#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
struct ListBase asset_shelves_settings
struct ListBase extension_repos
struct ListBase asset_libraries
i
Definition text_draw.cc:230
#define N_(msgid)