1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 */
16
17 /** \file
18 * \ingroup bke
19 *
20 * Access to application level directories.
21 */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "BLI_fileops.h"
28 #include "BLI_fileops_types.h"
29 #include "BLI_listbase.h"
30 #include "BLI_path_util.h"
31 #include "BLI_string.h"
32 #include "BLI_string_utf8.h"
33 #include "BLI_string_utils.h"
34 #include "BLI_utildefines.h"
35
36 #include "BKE_appdir.h" /* own include */
37 #include "BKE_blender_version.h"
38
39 #include "GHOST_Path-api.h"
40
41 #include "MEM_guardedalloc.h"
42
43 #include "CLG_log.h"
44
45 #ifdef WIN32
46 # include "utf_winfunc.h"
47 # include "utfconv.h"
48 # include <io.h>
49 # ifdef _WIN32_IE
50 # undef _WIN32_IE
51 # endif
52 # define _WIN32_IE 0x0501
53 # include "BLI_winstuff.h"
54 # include <shlobj.h>
55 # include <windows.h>
56 #else /* non windows */
57 # ifdef WITH_BINRELOC
58 # include "binreloc.h"
59 # endif
60 /* #mkdtemp on OSX (and probably all *BSD?), not worth making specific check for this OS. */
61 # include <unistd.h>
62 #endif /* WIN32 */
63
64 static const char _str_null[] = "(null)";
65 #define STR_OR_FALLBACK(a) ((a) ? (a) : _str_null)
66
67 /* -------------------------------------------------------------------- */
68 /** \name Local Variables
69 * \{ */
70
71 /* local */
72 static CLG_LogRef LOG = {"bke.appdir"};
73
74 static struct {
75 /** Full path to program executable. */
76 char program_filename[FILE_MAX];
77 /** Full path to directory in which executable is located. */
78 char program_dirname[FILE_MAX];
79 /** Persistent temporary directory (defined by the preferences or OS). */
80 char temp_dirname_base[FILE_MAX];
81 /** Volatile temporary directory (owned by Blender, removed on exit). */
82 char temp_dirname_session[FILE_MAX];
83 } g_app = {
84 .temp_dirname_session = "",
85 };
86
87 /** \} */
88
89 /* -------------------------------------------------------------------- */
90 /** \name Initialization
91 * \{ */
92
93 #ifndef NDEBUG
94 static bool is_appdir_init = false;
95 # define ASSERT_IS_INIT() BLI_assert(is_appdir_init)
96 #else
97 # define ASSERT_IS_INIT() ((void)0)
98 #endif
99
100 /**
101 * Sanity check to ensure correct API use in debug mode.
102 *
103 * Run this once the first level of arguments has been passed so we can be sure
104 * `--env-system-datafiles`, and other `--env-*` arguments has been passed.
105 *
106 * Without this any callers to this module that run early on,
107 * will miss out on changes from parsing arguments.
108 */
BKE_appdir_init(void)109 void BKE_appdir_init(void)
110 {
111 #ifndef NDEBUG
112 BLI_assert(is_appdir_init == false);
113 is_appdir_init = true;
114 #endif
115 }
116
117 /** \} */
118
119 /* -------------------------------------------------------------------- */
120 /** \name Internal Utilities
121 * \{ */
122
123 /**
124 * \returns a formatted representation of the specified version number. Non-re-entrant!
125 */
blender_version_decimal(const int version)126 static char *blender_version_decimal(const int version)
127 {
128 static char version_str[5];
129 BLI_assert(version < 1000);
130 BLI_snprintf(version_str, sizeof(version_str), "%d.%02d", version / 100, version % 100);
131 return version_str;
132 }
133
134 /** \} */
135
136 /* -------------------------------------------------------------------- */
137 /** \name Default Directories
138 * \{ */
139
140 /**
141 * This is now only used to really get the user's default document folder.
142 *
143 * \note On Windows `Users/{MyUserName}/Documents` is used
144 * as it's the default location to save documents.
145 */
BKE_appdir_folder_default(void)146 const char *BKE_appdir_folder_default(void)
147 {
148 #ifndef WIN32
149 const char *const xdg_documents_dir = BLI_getenv("XDG_DOCUMENTS_DIR");
150
151 if (xdg_documents_dir) {
152 return xdg_documents_dir;
153 }
154
155 return BLI_getenv("HOME");
156 #else /* Windows */
157 static char documentfolder[MAXPATHLEN];
158 HRESULT hResult;
159
160 /* Check for `%HOME%` environment variable. */
161 if (uput_getenv("HOME", documentfolder, MAXPATHLEN)) {
162 if (BLI_is_dir(documentfolder)) {
163 return documentfolder;
164 }
165 }
166
167 /* Add user profile support for WIN 2K / NT.
168 * This is `%APPDATA%`, which translates to either:
169 * - `%USERPROFILE%\Application Data` or...
170 * - `%USERPROFILE%\AppData\Roaming` (since Vista).
171 */
172 hResult = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, documentfolder);
173
174 if (hResult == S_OK) {
175 if (BLI_is_dir(documentfolder)) {
176 return documentfolder;
177 }
178 }
179
180 return NULL;
181 #endif /* WIN32 */
182 }
183
184 /**
185 * Gets a good default directory for fonts.
186 */
BKE_appdir_font_folder_default(char * dir)187 bool BKE_appdir_font_folder_default(
188 /* This parameter can only be `const` on non-windows platforms.
189 * NOLINTNEXTLINE: readability-non-const-parameter. */
190 char *dir)
191 {
192 bool success = false;
193 #ifdef WIN32
194 wchar_t wpath[FILE_MAXDIR];
195 success = SHGetSpecialFolderPathW(0, wpath, CSIDL_FONTS, 0);
196 if (success) {
197 wcscat(wpath, L"\\");
198 BLI_strncpy_wchar_as_utf8(dir, wpath, FILE_MAXDIR);
199 }
200 #endif
201 /* TODO: Values for other platforms. */
202 UNUSED_VARS(dir);
203 return success;
204 }
205
206 /** \} */
207
208 /* -------------------------------------------------------------------- */
209 /** \name Path Presets (Internal Helpers)
210 * \{ */
211
212 /**
213 * Concatenates paths into \a targetpath,
214 * returning true if result points to a directory.
215 *
216 * \param path_base: Path base, never NULL.
217 * \param folder_name: First sub-directory (optional).
218 * \param subfolder_name: Second sub-directory (optional).
219 * \param check_is_dir: When false, return true even if the path doesn't exist.
220 *
221 * \note The names for optional paths only follow other usage in this file,
222 * the names don't matter for this function.
223 *
224 * \note If it's useful we could take an arbitrary number of paths.
225 * For now usage is limited and we don't need this.
226 */
test_path(char * targetpath,size_t targetpath_len,const bool check_is_dir,const char * path_base,const char * folder_name,const char * subfolder_name)227 static bool test_path(char *targetpath,
228 size_t targetpath_len,
229 const bool check_is_dir,
230 const char *path_base,
231 const char *folder_name,
232 const char *subfolder_name)
233 {
234 ASSERT_IS_INIT();
235
236 /* Only the last argument should be NULL. */
237 BLI_assert(!(folder_name == NULL && (subfolder_name != NULL)));
238 BLI_path_join(targetpath, targetpath_len, path_base, folder_name, subfolder_name, NULL);
239 if (check_is_dir == false) {
240 CLOG_INFO(&LOG, 3, "using without test: '%s'", targetpath);
241 return true;
242 }
243
244 if (BLI_is_dir(targetpath)) {
245 CLOG_INFO(&LOG, 3, "found '%s'", targetpath);
246 return true;
247 }
248
249 CLOG_INFO(&LOG, 3, "missing '%s'", targetpath);
250
251 /* Path not found, don't accidentally use it,
252 * otherwise call this function with `check_is_dir` set to false. */
253 targetpath[0] = '\0';
254 return false;
255 }
256
257 /**
258 * Puts the value of the specified environment variable into \a path if it exists.
259 *
260 * \param check_is_dir: When true, checks if it points at a directory.
261 *
262 * \returns true when the value of the environment variable is stored
263 * at the address \a path points to.
264 */
test_env_path(char * path,const char * envvar,const bool check_is_dir)265 static bool test_env_path(char *path, const char *envvar, const bool check_is_dir)
266 {
267 ASSERT_IS_INIT();
268
269 const char *env_path = envvar ? BLI_getenv(envvar) : NULL;
270 if (!env_path) {
271 return false;
272 }
273
274 BLI_strncpy(path, env_path, FILE_MAX);
275
276 if (check_is_dir == false) {
277 CLOG_INFO(&LOG, 3, "using env '%s' without test: '%s'", envvar, env_path);
278 return true;
279 }
280
281 if (BLI_is_dir(env_path)) {
282 CLOG_INFO(&LOG, 3, "env '%s' found: %s", envvar, env_path);
283 return true;
284 }
285
286 CLOG_INFO(&LOG, 3, "env '%s' missing: %s", envvar, env_path);
287
288 /* Path not found, don't accidentally use it,
289 * otherwise call this function with `check_is_dir` set to false. */
290 path[0] = '\0';
291 return false;
292 }
293
294 /**
295 * Constructs in \a targetpath the name of a directory relative to a version-specific
296 * sub-directory in the parent directory of the Blender executable.
297 *
298 * \param targetpath: String to return path.
299 * \param folder_name: Optional folder name within version-specific directory.
300 * \param subfolder_name: Optional sub-folder name within folder_name.
301 *
302 * \param version: To construct name of version-specific directory within #g_app.program_dirname.
303 * \param check_is_dir: When false, return true even if the path doesn't exist.
304 * \return true if such a directory exists.
305 */
get_path_local_ex(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name,const int version,const bool check_is_dir)306 static bool get_path_local_ex(char *targetpath,
307 size_t targetpath_len,
308 const char *folder_name,
309 const char *subfolder_name,
310 const int version,
311 const bool check_is_dir)
312 {
313 char relfolder[FILE_MAX];
314
315 CLOG_INFO(&LOG,
316 3,
317 "folder='%s', subfolder='%s'",
318 STR_OR_FALLBACK(folder_name),
319 STR_OR_FALLBACK(subfolder_name));
320
321 if (folder_name) { /* `subfolder_name` may be NULL. */
322 BLI_path_join(relfolder, sizeof(relfolder), folder_name, subfolder_name, NULL);
323 }
324 else {
325 relfolder[0] = '\0';
326 }
327
328 /* Try `{g_app.program_dirname}/2.xx/{folder_name}` the default directory
329 * for a portable distribution. See `WITH_INSTALL_PORTABLE` build-option. */
330 const char *path_base = g_app.program_dirname;
331 #ifdef __APPLE__
332 /* Due new code-sign situation in OSX > 10.9.5
333 * we must move the blender_version dir with contents to Resources. */
334 char osx_resourses[FILE_MAX];
335 BLI_snprintf(osx_resourses, sizeof(osx_resourses), "%s../Resources", g_app.program_dirname);
336 /* Remove the '/../' added above. */
337 BLI_path_normalize(NULL, osx_resourses);
338 path_base = osx_resourses;
339 #endif
340 return test_path(targetpath,
341 targetpath_len,
342 check_is_dir,
343 path_base,
344 blender_version_decimal(version),
345 relfolder);
346 }
get_path_local(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name)347 static bool get_path_local(char *targetpath,
348 size_t targetpath_len,
349 const char *folder_name,
350 const char *subfolder_name)
351 {
352 const int version = BLENDER_VERSION;
353 const bool check_is_dir = true;
354 return get_path_local_ex(
355 targetpath, targetpath_len, folder_name, subfolder_name, version, check_is_dir);
356 }
357
358 /**
359 * Check if this is an install with user files kept together
360 * with the Blender executable and its installation files.
361 */
BKE_appdir_app_is_portable_install(void)362 bool BKE_appdir_app_is_portable_install(void)
363 {
364 /* Detect portable install by the existence of `config` folder. */
365 char path[FILE_MAX];
366 return get_path_local(path, sizeof(path), "config", NULL);
367 }
368
369 /**
370 * Returns the path of a folder from environment variables.
371 *
372 * \param targetpath: String to return path.
373 * \param subfolder_name: optional name of sub-folder within folder.
374 * \param envvar: name of environment variable to check folder_name.
375 * \param check_is_dir: When false, return true even if the path doesn't exist.
376 * \return true if it was able to construct such a path and the path exists.
377 */
get_path_environment_ex(char * targetpath,size_t targetpath_len,const char * subfolder_name,const char * envvar,const bool check_is_dir)378 static bool get_path_environment_ex(char *targetpath,
379 size_t targetpath_len,
380 const char *subfolder_name,
381 const char *envvar,
382 const bool check_is_dir)
383 {
384 char user_path[FILE_MAX];
385
386 if (test_env_path(user_path, envvar, check_is_dir)) {
387 /* Note that `subfolder_name` may be NULL, in this case we use `user_path` as-is. */
388 return test_path(targetpath, targetpath_len, check_is_dir, user_path, subfolder_name, NULL);
389 }
390 return false;
391 }
get_path_environment(char * targetpath,size_t targetpath_len,const char * subfolder_name,const char * envvar)392 static bool get_path_environment(char *targetpath,
393 size_t targetpath_len,
394 const char *subfolder_name,
395 const char *envvar)
396 {
397 const bool check_is_dir = true;
398 return get_path_environment_ex(targetpath, targetpath_len, subfolder_name, envvar, check_is_dir);
399 }
400
401 /**
402 * Returns the path of a folder within the user-files area.
403 *
404 * \param targetpath: String to return path.
405 * \param folder_name: default name of folder within user area.
406 * \param subfolder_name: optional name of sub-folder within folder.
407 * \param version: Blender version, used to construct a sub-directory name.
408 * \param check_is_dir: When false, return true even if the path doesn't exist.
409 * \return true if it was able to construct such a path.
410 */
get_path_user_ex(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name,const int version,const bool check_is_dir)411 static bool get_path_user_ex(char *targetpath,
412 size_t targetpath_len,
413 const char *folder_name,
414 const char *subfolder_name,
415 const int version,
416 const bool check_is_dir)
417 {
418 char user_path[FILE_MAX];
419 const char *user_base_path;
420
421 /* for portable install, user path is always local */
422 if (BKE_appdir_app_is_portable_install()) {
423 return get_path_local_ex(
424 targetpath, targetpath_len, folder_name, subfolder_name, version, check_is_dir);
425 }
426 user_path[0] = '\0';
427
428 user_base_path = (const char *)GHOST_getUserDir(version, blender_version_decimal(version));
429 if (user_base_path) {
430 BLI_strncpy(user_path, user_base_path, FILE_MAX);
431 }
432
433 if (!user_path[0]) {
434 return false;
435 }
436
437 CLOG_INFO(&LOG,
438 3,
439 "'%s', folder='%s', subfolder='%s'",
440 user_path,
441 STR_OR_FALLBACK(folder_name),
442 STR_OR_FALLBACK(subfolder_name));
443
444 /* `subfolder_name` may be NULL. */
445 return test_path(
446 targetpath, targetpath_len, check_is_dir, user_path, folder_name, subfolder_name);
447 }
get_path_user(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name)448 static bool get_path_user(char *targetpath,
449 size_t targetpath_len,
450 const char *folder_name,
451 const char *subfolder_name)
452 {
453 const int version = BLENDER_VERSION;
454 const bool check_is_dir = true;
455 return get_path_user_ex(
456 targetpath, targetpath_len, folder_name, subfolder_name, version, check_is_dir);
457 }
458
459 /**
460 * Returns the path of a folder within the Blender installation directory.
461 *
462 * \param targetpath: String to return path.
463 * \param folder_name: default name of folder within installation area.
464 * \param subfolder_name: optional name of sub-folder within folder.
465 * \param version: Blender version, used to construct a sub-directory name.
466 * \param check_is_dir: When false, return true even if the path doesn't exist.
467 * \return true if it was able to construct such a path.
468 */
get_path_system_ex(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name,const int version,const bool check_is_dir)469 static bool get_path_system_ex(char *targetpath,
470 size_t targetpath_len,
471 const char *folder_name,
472 const char *subfolder_name,
473 const int version,
474 const bool check_is_dir)
475 {
476 char system_path[FILE_MAX];
477 const char *system_base_path;
478 char relfolder[FILE_MAX];
479
480 if (folder_name) { /* `subfolder_name` may be NULL. */
481 BLI_path_join(relfolder, sizeof(relfolder), folder_name, subfolder_name, NULL);
482 }
483 else {
484 relfolder[0] = '\0';
485 }
486
487 system_path[0] = '\0';
488 system_base_path = (const char *)GHOST_getSystemDir(version, blender_version_decimal(version));
489 if (system_base_path) {
490 BLI_strncpy(system_path, system_base_path, FILE_MAX);
491 }
492
493 if (!system_path[0]) {
494 return false;
495 }
496
497 CLOG_INFO(&LOG,
498 3,
499 "'%s', folder='%s', subfolder='%s'",
500 system_path,
501 STR_OR_FALLBACK(folder_name),
502 STR_OR_FALLBACK(subfolder_name));
503
504 /* Try `$BLENDERPATH/folder_name/subfolder_name`, `subfolder_name` may be NULL. */
505 return test_path(
506 targetpath, targetpath_len, check_is_dir, system_path, folder_name, subfolder_name);
507 }
508
get_path_system(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name)509 static bool get_path_system(char *targetpath,
510 size_t targetpath_len,
511 const char *folder_name,
512 const char *subfolder_name)
513 {
514 const int version = BLENDER_VERSION;
515 const bool check_is_dir = true;
516 return get_path_system_ex(
517 targetpath, targetpath_len, folder_name, subfolder_name, version, check_is_dir);
518 }
519
520 /** \} */
521
522 /* -------------------------------------------------------------------- */
523 /** \name Path Presets API
524 * \{ */
525
526 /**
527 * Get a folder out of the \a folder_id presets for paths.
528 *
529 * \param subfolder: The name of a directory to check for,
530 * this may contain path separators but must resolve to a directory, checked with #BLI_is_dir.
531 * \return The path if found, NULL string if not.
532 */
BKE_appdir_folder_id_ex(const int folder_id,const char * subfolder,char * path,size_t path_len)533 bool BKE_appdir_folder_id_ex(const int folder_id,
534 const char *subfolder,
535 char *path,
536 size_t path_len)
537 {
538 switch (folder_id) {
539 case BLENDER_DATAFILES: /* general case */
540 if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) {
541 break;
542 }
543 if (get_path_user(path, path_len, "datafiles", subfolder)) {
544 break;
545 }
546 if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_DATAFILES")) {
547 break;
548 }
549 if (get_path_local(path, path_len, "datafiles", subfolder)) {
550 break;
551 }
552 if (get_path_system(path, path_len, "datafiles", subfolder)) {
553 break;
554 }
555 return false;
556
557 case BLENDER_USER_DATAFILES:
558 if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) {
559 break;
560 }
561 if (get_path_user(path, path_len, "datafiles", subfolder)) {
562 break;
563 }
564 return false;
565
566 case BLENDER_SYSTEM_DATAFILES:
567 if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_DATAFILES")) {
568 break;
569 }
570 if (get_path_system(path, path_len, "datafiles", subfolder)) {
571 break;
572 }
573 if (get_path_local(path, path_len, "datafiles", subfolder)) {
574 break;
575 }
576 return false;
577
578 case BLENDER_USER_AUTOSAVE:
579 if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) {
580 break;
581 }
582 if (get_path_user(path, path_len, "autosave", subfolder)) {
583 break;
584 }
585 return false;
586
587 case BLENDER_USER_CONFIG:
588 if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_CONFIG")) {
589 break;
590 }
591 if (get_path_user(path, path_len, "config", subfolder)) {
592 break;
593 }
594 return false;
595
596 case BLENDER_USER_SCRIPTS:
597 if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_SCRIPTS")) {
598 break;
599 }
600 if (get_path_user(path, path_len, "scripts", subfolder)) {
601 break;
602 }
603 return false;
604
605 case BLENDER_SYSTEM_SCRIPTS:
606 if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_SCRIPTS")) {
607 break;
608 }
609 if (get_path_system(path, path_len, "scripts", subfolder)) {
610 break;
611 }
612 if (get_path_local(path, path_len, "scripts", subfolder)) {
613 break;
614 }
615 return false;
616
617 case BLENDER_SYSTEM_PYTHON:
618 if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_PYTHON")) {
619 break;
620 }
621 if (get_path_system(path, path_len, "python", subfolder)) {
622 break;
623 }
624 if (get_path_local(path, path_len, "python", subfolder)) {
625 break;
626 }
627 return false;
628
629 default:
630 BLI_assert(0);
631 break;
632 }
633
634 return true;
635 }
636
BKE_appdir_folder_id(const int folder_id,const char * subfolder)637 const char *BKE_appdir_folder_id(const int folder_id, const char *subfolder)
638 {
639 static char path[FILE_MAX] = "";
640 if (BKE_appdir_folder_id_ex(folder_id, subfolder, path, sizeof(path))) {
641 return path;
642 }
643 return NULL;
644 }
645
646 /**
647 * Returns the path to a folder in the user area without checking that it actually exists first.
648 */
BKE_appdir_folder_id_user_notest(const int folder_id,const char * subfolder)649 const char *BKE_appdir_folder_id_user_notest(const int folder_id, const char *subfolder)
650 {
651 const int version = BLENDER_VERSION;
652 static char path[FILE_MAX] = "";
653 const bool check_is_dir = false;
654
655 switch (folder_id) {
656 case BLENDER_USER_DATAFILES:
657 if (get_path_environment_ex(
658 path, sizeof(path), subfolder, "BLENDER_USER_DATAFILES", check_is_dir)) {
659 break;
660 }
661 get_path_user_ex(path, sizeof(path), "datafiles", subfolder, version, check_is_dir);
662 break;
663 case BLENDER_USER_CONFIG:
664 if (get_path_environment_ex(
665 path, sizeof(path), subfolder, "BLENDER_USER_CONFIG", check_is_dir)) {
666 break;
667 }
668 get_path_user_ex(path, sizeof(path), "config", subfolder, version, check_is_dir);
669 break;
670 case BLENDER_USER_AUTOSAVE:
671 if (get_path_environment_ex(
672 path, sizeof(path), subfolder, "BLENDER_USER_AUTOSAVE", check_is_dir)) {
673 break;
674 }
675 get_path_user_ex(path, sizeof(path), "autosave", subfolder, version, check_is_dir);
676 break;
677 case BLENDER_USER_SCRIPTS:
678 if (get_path_environment_ex(
679 path, sizeof(path), subfolder, "BLENDER_USER_SCRIPTS", check_is_dir)) {
680 break;
681 }
682 get_path_user_ex(path, sizeof(path), "scripts", subfolder, version, check_is_dir);
683 break;
684 default:
685 BLI_assert(0);
686 break;
687 }
688
689 if ('\0' == path[0]) {
690 return NULL;
691 }
692 return path;
693 }
694
695 /**
696 * Returns the path to a folder in the user area, creating it if it doesn't exist.
697 */
BKE_appdir_folder_id_create(const int folder_id,const char * subfolder)698 const char *BKE_appdir_folder_id_create(const int folder_id, const char *subfolder)
699 {
700 const char *path;
701
702 /* Only for user folders. */
703 if (!ELEM(folder_id,
704 BLENDER_USER_DATAFILES,
705 BLENDER_USER_CONFIG,
706 BLENDER_USER_SCRIPTS,
707 BLENDER_USER_AUTOSAVE)) {
708 return NULL;
709 }
710
711 path = BKE_appdir_folder_id(folder_id, subfolder);
712
713 if (!path) {
714 path = BKE_appdir_folder_id_user_notest(folder_id, subfolder);
715 if (path) {
716 BLI_dir_create_recursive(path);
717 }
718 }
719
720 return path;
721 }
722
723 /**
724 * Returns the path of the top-level version-specific local, user or system directory.
725 * If check_is_dir, then the result will be NULL if the directory doesn't exist.
726 */
BKE_appdir_folder_id_version(const int folder_id,const int version,const bool check_is_dir)727 const char *BKE_appdir_folder_id_version(const int folder_id,
728 const int version,
729 const bool check_is_dir)
730 {
731 static char path[FILE_MAX] = "";
732 bool ok;
733 switch (folder_id) {
734 case BLENDER_RESOURCE_PATH_USER:
735 ok = get_path_user_ex(path, sizeof(path), NULL, NULL, version, check_is_dir);
736 break;
737 case BLENDER_RESOURCE_PATH_LOCAL:
738 ok = get_path_local_ex(path, sizeof(path), NULL, NULL, version, check_is_dir);
739 break;
740 case BLENDER_RESOURCE_PATH_SYSTEM:
741 ok = get_path_system_ex(path, sizeof(path), NULL, NULL, version, check_is_dir);
742 break;
743 default:
744 path[0] = '\0'; /* in case check_is_dir is false */
745 ok = false;
746 BLI_assert(!"incorrect ID");
747 break;
748 }
749 return ok ? path : NULL;
750 }
751
752 /** \} */
753
754 /* -------------------------------------------------------------------- */
755 /** \name Program Path Queries
756 *
757 * Access locations of Blender & Python.
758 * \{ */
759
760 /**
761 * Checks if name is a fully qualified filename to an executable.
762 * If not it searches `$PATH` for the file. On Windows it also
763 * adds the correct extension (`.com` `.exe` etc) from
764 * `$PATHEXT` if necessary. Also on Windows it translates
765 * the name to its 8.3 version to prevent problems with
766 * spaces and stuff. Final result is returned in \a fullname.
767 *
768 * \param fullname: The full path and full name of the executable
769 * (must be #FILE_MAX minimum)
770 * \param name: The name of the executable (usually `argv[0]`) to be checked
771 */
where_am_i(char * fullname,const size_t maxlen,const char * name)772 static void where_am_i(char *fullname, const size_t maxlen, const char *name)
773 {
774 #ifdef WITH_BINRELOC
775 /* Linux uses `binreloc` since `argv[0]` is not reliable, call `br_init(NULL)` first. */
776 {
777 const char *path = NULL;
778 path = br_find_exe(NULL);
779 if (path) {
780 BLI_strncpy(fullname, path, maxlen);
781 free((void *)path);
782 return;
783 }
784 }
785 #endif
786
787 #ifdef _WIN32
788 {
789 wchar_t *fullname_16 = MEM_mallocN(maxlen * sizeof(wchar_t), "ProgramPath");
790 if (GetModuleFileNameW(0, fullname_16, maxlen)) {
791 conv_utf_16_to_8(fullname_16, fullname, maxlen);
792 if (!BLI_exists(fullname)) {
793 CLOG_ERROR(&LOG, "path can't be found: \"%.*s\"", (int)maxlen, fullname);
794 MessageBox(
795 NULL, "path contains invalid characters or is too long (see console)", "Error", MB_OK);
796 }
797 MEM_freeN(fullname_16);
798 return;
799 }
800
801 MEM_freeN(fullname_16);
802 }
803 #endif
804
805 /* Unix and non Linux. */
806 if (name && name[0]) {
807
808 BLI_strncpy(fullname, name, maxlen);
809 if (name[0] == '.') {
810 BLI_path_abs_from_cwd(fullname, maxlen);
811 #ifdef _WIN32
812 BLI_path_program_extensions_add_win32(fullname, maxlen);
813 #endif
814 }
815 else if (BLI_path_slash_rfind(name)) {
816 /* Full path. */
817 BLI_strncpy(fullname, name, maxlen);
818 #ifdef _WIN32
819 BLI_path_program_extensions_add_win32(fullname, maxlen);
820 #endif
821 }
822 else {
823 BLI_path_program_search(fullname, maxlen, name);
824 }
825 /* Remove "/./" and "/../" so string comparisons can be used on the path. */
826 BLI_path_normalize(NULL, fullname);
827
828 #if defined(DEBUG)
829 if (!STREQ(name, fullname)) {
830 CLOG_INFO(&LOG, 2, "guessing '%s' == '%s'", name, fullname);
831 }
832 #endif
833 }
834 }
835
BKE_appdir_program_path_init(const char * argv0)836 void BKE_appdir_program_path_init(const char *argv0)
837 {
838 where_am_i(g_app.program_filename, sizeof(g_app.program_filename), argv0);
839 BLI_split_dir_part(g_app.program_filename, g_app.program_dirname, sizeof(g_app.program_dirname));
840 }
841
842 /**
843 * Path to executable
844 */
BKE_appdir_program_path(void)845 const char *BKE_appdir_program_path(void)
846 {
847 BLI_assert(g_app.program_filename[0]);
848 return g_app.program_filename;
849 }
850
851 /**
852 * Path to directory of executable
853 */
BKE_appdir_program_dir(void)854 const char *BKE_appdir_program_dir(void)
855 {
856 BLI_assert(g_app.program_dirname[0]);
857 return g_app.program_dirname;
858 }
859
BKE_appdir_program_python_search(char * fullpath,const size_t fullpath_len,const int version_major,const int version_minor)860 bool BKE_appdir_program_python_search(char *fullpath,
861 const size_t fullpath_len,
862 const int version_major,
863 const int version_minor)
864 {
865 ASSERT_IS_INIT();
866
867 #ifdef PYTHON_EXECUTABLE_NAME
868 /* Passed in from the build-systems 'PYTHON_EXECUTABLE'. */
869 const char *python_build_def = STRINGIFY(PYTHON_EXECUTABLE_NAME);
870 #endif
871 const char *basename = "python";
872 char python_version[16];
873 /* Check both possible names. */
874 const char *python_names[] = {
875 #ifdef PYTHON_EXECUTABLE_NAME
876 python_build_def,
877 #endif
878 python_version,
879 basename,
880 };
881 bool is_found = false;
882
883 SNPRINTF(python_version, "%s%d.%d", basename, version_major, version_minor);
884
885 {
886 const char *python_bin_dir = BKE_appdir_folder_id(BLENDER_SYSTEM_PYTHON, "bin");
887 if (python_bin_dir) {
888
889 for (int i = 0; i < ARRAY_SIZE(python_names); i++) {
890 BLI_join_dirfile(fullpath, fullpath_len, python_bin_dir, python_names[i]);
891
892 if (
893 #ifdef _WIN32
894 BLI_path_program_extensions_add_win32(fullpath, fullpath_len)
895 #else
896 BLI_exists(fullpath)
897 #endif
898 ) {
899 is_found = true;
900 break;
901 }
902 }
903 }
904 }
905
906 if (is_found == false) {
907 for (int i = 0; i < ARRAY_SIZE(python_names); i++) {
908 if (BLI_path_program_search(fullpath, fullpath_len, python_names[i])) {
909 is_found = true;
910 break;
911 }
912 }
913 }
914
915 if (is_found == false) {
916 *fullpath = '\0';
917 }
918
919 return is_found;
920 }
921
922 /** \} */
923
924 /* -------------------------------------------------------------------- */
925 /** \name Application Templates
926 * \{ */
927
928 /** Keep in sync with `bpy.utils.app_template_paths()` */
929 static const char *app_template_directory_search[2] = {
930 "startup" SEP_STR "bl_app_templates_user",
931 "startup" SEP_STR "bl_app_templates_system",
932 };
933
934 static const int app_template_directory_id[2] = {
935 /* Only 'USER' */
936 BLENDER_USER_SCRIPTS,
937 /* Covers 'LOCAL' & 'SYSTEM'. */
938 BLENDER_SYSTEM_SCRIPTS,
939 };
940
941 /**
942 * Return true if templates exist
943 */
BKE_appdir_app_template_any(void)944 bool BKE_appdir_app_template_any(void)
945 {
946 char temp_dir[FILE_MAX];
947 for (int i = 0; i < 2; i++) {
948 if (BKE_appdir_folder_id_ex(app_template_directory_id[i],
949 app_template_directory_search[i],
950 temp_dir,
951 sizeof(temp_dir))) {
952 return true;
953 }
954 }
955 return false;
956 }
957
BKE_appdir_app_template_id_search(const char * app_template,char * path,size_t path_len)958 bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_len)
959 {
960 for (int i = 0; i < 2; i++) {
961 char subdir[FILE_MAX];
962 BLI_join_dirfile(subdir, sizeof(subdir), app_template_directory_search[i], app_template);
963 if (BKE_appdir_folder_id_ex(app_template_directory_id[i], subdir, path, path_len)) {
964 return true;
965 }
966 }
967 return false;
968 }
969
BKE_appdir_app_template_has_userpref(const char * app_template)970 bool BKE_appdir_app_template_has_userpref(const char *app_template)
971 {
972 /* Test if app template provides a `userpref.blend`.
973 * If not, we will share user preferences with the rest of Blender. */
974 if (!app_template && app_template[0]) {
975 return false;
976 }
977
978 char app_template_path[FILE_MAX];
979 if (!BKE_appdir_app_template_id_search(
980 app_template, app_template_path, sizeof(app_template_path))) {
981 return false;
982 }
983
984 char userpref_path[FILE_MAX];
985 BLI_path_join(
986 userpref_path, sizeof(userpref_path), app_template_path, BLENDER_USERPREF_FILE, NULL);
987 return BLI_exists(userpref_path);
988 }
989
BKE_appdir_app_templates(ListBase * templates)990 void BKE_appdir_app_templates(ListBase *templates)
991 {
992 BLI_listbase_clear(templates);
993
994 for (int i = 0; i < 2; i++) {
995 char subdir[FILE_MAX];
996 if (!BKE_appdir_folder_id_ex(app_template_directory_id[i],
997 app_template_directory_search[i],
998 subdir,
999 sizeof(subdir))) {
1000 continue;
1001 }
1002
1003 struct direntry *dir;
1004 uint totfile = BLI_filelist_dir_contents(subdir, &dir);
1005 for (int f = 0; f < totfile; f++) {
1006 if (!FILENAME_IS_CURRPAR(dir[f].relname) && S_ISDIR(dir[f].type)) {
1007 char *template = BLI_strdup(dir[f].relname);
1008 BLI_addtail(templates, BLI_genericNodeN(template));
1009 }
1010 }
1011
1012 BLI_filelist_free(dir, totfile);
1013 }
1014 }
1015
1016 /** \} */
1017
1018 /* -------------------------------------------------------------------- */
1019 /** \name Temporary Directories
1020 * \{ */
1021
1022 /**
1023 * Gets the temp directory when blender first runs.
1024 * If the default path is not found, use try $TEMP
1025 *
1026 * Also make sure the temp dir has a trailing slash
1027 *
1028 * \param tempdir: The full path to the temporary temp directory.
1029 * \param tempdir_len: The size of the \a tempdir buffer.
1030 * \param userdir: Directory specified in user preferences (may be NULL).
1031 * note that by default this is an empty string, only use when non-empty.
1032 */
where_is_temp(char * tempdir,const size_t tempdir_len,const char * userdir)1033 static void where_is_temp(char *tempdir, const size_t tempdir_len, const char *userdir)
1034 {
1035
1036 tempdir[0] = '\0';
1037
1038 if (userdir && BLI_is_dir(userdir)) {
1039 BLI_strncpy(tempdir, userdir, tempdir_len);
1040 }
1041
1042 if (tempdir[0] == '\0') {
1043 const char *env_vars[] = {
1044 #ifdef WIN32
1045 "TEMP",
1046 #else
1047 /* Non standard (could be removed). */
1048 "TMP",
1049 /* Posix standard. */
1050 "TMPDIR",
1051 #endif
1052 };
1053 for (int i = 0; i < ARRAY_SIZE(env_vars); i++) {
1054 const char *tmp = BLI_getenv(env_vars[i]);
1055 if (tmp && (tmp[0] != '\0') && BLI_is_dir(tmp)) {
1056 BLI_strncpy(tempdir, tmp, tempdir_len);
1057 break;
1058 }
1059 }
1060 }
1061
1062 if (tempdir[0] == '\0') {
1063 BLI_strncpy(tempdir, "/tmp/", tempdir_len);
1064 }
1065 else {
1066 /* add a trailing slash if needed */
1067 BLI_path_slash_ensure(tempdir);
1068 }
1069 }
1070
tempdir_session_create(char * tempdir_session,const size_t tempdir_session_len,const char * tempdir)1071 static void tempdir_session_create(char *tempdir_session,
1072 const size_t tempdir_session_len,
1073 const char *tempdir)
1074 {
1075 tempdir_session[0] = '\0';
1076
1077 const int tempdir_len = strlen(tempdir);
1078 /* 'XXXXXX' is kind of tag to be replaced by `mktemp-family` by an UUID. */
1079 const char *session_name = "blender_XXXXXX";
1080 const int session_name_len = strlen(session_name);
1081
1082 /* +1 as a slash is added,
1083 * #_mktemp_s also requires the last null character is included. */
1084 const int tempdir_session_len_required = tempdir_len + session_name_len + 1;
1085
1086 if (tempdir_session_len_required <= tempdir_session_len) {
1087 /* No need to use path joining utility as we know the last character of #tempdir is a slash. */
1088 BLI_string_join(tempdir_session, tempdir_session_len, tempdir, session_name);
1089 #ifdef WIN32
1090 const bool needs_create = (_mktemp_s(tempdir_session, tempdir_session_len_required) == 0);
1091 #else
1092 const bool needs_create = (mkdtemp(tempdir_session) == NULL);
1093 #endif
1094 if (needs_create) {
1095 BLI_dir_create_recursive(tempdir_session);
1096 }
1097 if (BLI_is_dir(tempdir_session)) {
1098 BLI_path_slash_ensure(tempdir_session);
1099 /* Success. */
1100 return;
1101 }
1102 }
1103
1104 CLOG_WARN(&LOG,
1105 "Could not generate a temp file name for '%s', falling back to '%s'",
1106 tempdir_session,
1107 tempdir);
1108 BLI_strncpy(tempdir_session, tempdir, tempdir_session_len);
1109 }
1110
1111 /**
1112 * Sets #g_app.temp_dirname_base to \a userdir if specified and is a valid directory,
1113 * otherwise chooses a suitable OS-specific temporary directory.
1114 * Sets #g_app.temp_dirname_session to a #mkdtemp
1115 * generated sub-dir of #g_app.temp_dirname_base.
1116 */
BKE_tempdir_init(const char * userdir)1117 void BKE_tempdir_init(const char *userdir)
1118 {
1119 where_is_temp(g_app.temp_dirname_base, sizeof(g_app.temp_dirname_base), userdir);
1120
1121 /* Clear existing temp dir, if needed. */
1122 BKE_tempdir_session_purge();
1123 /* Now that we have a valid temp dir, add system-generated unique sub-dir. */
1124 tempdir_session_create(
1125 g_app.temp_dirname_session, sizeof(g_app.temp_dirname_session), g_app.temp_dirname_base);
1126 }
1127
1128 /**
1129 * Path to temporary directory (with trailing slash)
1130 */
BKE_tempdir_session(void)1131 const char *BKE_tempdir_session(void)
1132 {
1133 return g_app.temp_dirname_session[0] ? g_app.temp_dirname_session : BKE_tempdir_base();
1134 }
1135
1136 /**
1137 * Path to persistent temporary directory (with trailing slash)
1138 */
BKE_tempdir_base(void)1139 const char *BKE_tempdir_base(void)
1140 {
1141 return g_app.temp_dirname_base;
1142 }
1143
1144 /**
1145 * Delete content of this instance's temp dir.
1146 */
BKE_tempdir_session_purge(void)1147 void BKE_tempdir_session_purge(void)
1148 {
1149 if (g_app.temp_dirname_session[0] && BLI_is_dir(g_app.temp_dirname_session)) {
1150 BLI_delete(g_app.temp_dirname_session, true, true);
1151 }
1152 }
1153
1154 /** \} */
1155