2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 /** \file blender/blenkernel/intern/appdir.c
23 * Access to application level directories.
30 #include "BLI_utildefines.h"
31 #include "BLI_fileops.h"
32 #include "BLI_fileops_types.h"
33 #include "BLI_listbase.h"
34 #include "BLI_path_util.h"
35 #include "BLI_string.h"
37 #include "BKE_blender_version.h"
38 #include "BKE_appdir.h" /* own include */
40 #include "GHOST_Path-api.h"
42 #include "MEM_guardedalloc.h"
45 # include "utf_winfunc.h"
51 # define _WIN32_IE 0x0501
54 # include "BLI_winstuff.h"
55 #else /* non windows */
57 # include "binreloc.h"
59 # include <unistd.h> /* mkdtemp on OSX (and probably all *BSD?), not worth making specific check for this OS. */
63 static char bprogname[FILE_MAX]; /* full path to program executable */
64 static char bprogdir[FILE_MAX]; /* full path to directory in which executable is located */
65 static char btempdir_base[FILE_MAX]; /* persistent temporary directory */
66 static char btempdir_session[FILE_MAX] = ""; /* volatile temporary directory */
68 /* This is now only used to really get the user's default document folder */
69 /* On Windows I chose the 'Users/<MyUserName>/Documents' since it's used
70 * as default location to save documents */
71 const char *BKE_appdir_folder_default(void)
74 const char * const xdg_documents_dir = BLI_getenv("XDG_DOCUMENTS_DIR");
76 if (xdg_documents_dir)
77 return xdg_documents_dir;
79 return BLI_getenv("HOME");
81 static char documentfolder[MAXPATHLEN];
84 /* Check for %HOME% env var */
85 if (uput_getenv("HOME", documentfolder, MAXPATHLEN)) {
86 if (BLI_is_dir(documentfolder)) return documentfolder;
89 /* add user profile support for WIN 2K / NT.
90 * This is %APPDATA%, which translates to either
91 * %USERPROFILE%\Application Data or since Vista
92 * to %USERPROFILE%\AppData\Roaming
94 hResult = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, documentfolder);
96 if (hResult == S_OK) {
97 if (BLI_is_dir(documentfolder)) return documentfolder;
105 // #define PATH_DEBUG
107 /* returns a formatted representation of the specified version number. Non-reentrant! */
108 static char *blender_version_decimal(const int ver)
110 static char version_str[5];
111 BLI_assert(ver < 1000);
112 BLI_snprintf(version_str, sizeof(version_str), "%d.%02d", ver / 100, ver % 100);
117 * Concatenates path_base, (optional) path_sep and (optional) folder_name into targetpath,
118 * returning true if result points to a directory.
120 static bool test_path(
121 char *targetpath, size_t targetpath_len,
122 const char *path_base, const char *path_sep, const char *folder_name)
124 char tmppath[FILE_MAX];
127 BLI_join_dirfile(tmppath, sizeof(tmppath), path_base, path_sep);
130 BLI_strncpy(tmppath, path_base, sizeof(tmppath));
133 /* rare cases folder_name is omitted (when looking for ~/.config/blender/2.xx dir only) */
135 BLI_join_dirfile(targetpath, targetpath_len, tmppath, folder_name);
138 BLI_strncpy(targetpath, tmppath, targetpath_len);
140 /* FIXME: why is "//" on front of tmppath expanded to "/" (by BLI_join_dirfile)
141 * if folder_name is specified but not otherwise? */
143 if (BLI_is_dir(targetpath)) {
145 printf("\t%s found: %s\n", __func__, targetpath);
151 printf("\t%s missing: %s\n", __func__, targetpath);
153 //targetpath[0] = '\0';
159 * Puts the value of the specified environment variable into *path if it exists
160 * and points at a directory. Returns true if this was done.
162 static bool test_env_path(char *path, const char *envvar)
164 const char *env = envvar ? BLI_getenv(envvar) : NULL;
165 if (!env) return false;
167 if (BLI_is_dir(env)) {
168 BLI_strncpy(path, env, FILE_MAX);
170 printf("\t%s env %s found: %s\n", __func__, envvar, env);
177 printf("\t%s env %s missing: %s\n", __func__, envvar, env);
184 * Constructs in \a targetpath the name of a directory relative to a version-specific
185 * subdirectory in the parent directory of the Blender executable.
187 * \param targetpath String to return path
188 * \param folder_name Optional folder name within version-specific directory
189 * \param subfolder_name Optional subfolder name within folder_name
190 * \param ver To construct name of version-specific directory within bprogdir
191 * \return true if such a directory exists.
193 static bool get_path_local(
194 char *targetpath, size_t targetpath_len,
195 const char *folder_name, const char *subfolder_name, const int ver)
197 char relfolder[FILE_MAX];
200 printf("%s...\n", __func__);
204 if (subfolder_name) {
205 BLI_join_dirfile(relfolder, sizeof(relfolder), folder_name, subfolder_name);
208 BLI_strncpy(relfolder, folder_name, sizeof(relfolder));
215 /* try EXECUTABLE_DIR/2.5x/folder_name - new default directory for local blender installed files */
217 /* due new codesign situation in OSX > 10.9.5 we must move the blender_version dir with contents to Resources */
218 char osx_resourses[FILE_MAX];
219 BLI_snprintf(osx_resourses, sizeof(osx_resourses), "%s../Resources", bprogdir);
220 /* Remove the '/../' added above. */
221 BLI_cleanup_path(NULL, osx_resourses);
222 return test_path(targetpath, targetpath_len, osx_resourses, blender_version_decimal(ver), relfolder);
224 return test_path(targetpath, targetpath_len, bprogdir, blender_version_decimal(ver), relfolder);
229 * Is this an install with user files kept together with the Blender executable and its
230 * installation files.
232 bool BKE_appdir_app_is_portable_install(void)
234 /* detect portable install by the existence of config folder */
235 const int ver = BLENDER_VERSION;
238 return get_path_local(path, sizeof(path), "config", NULL, ver);
242 * Returns the path of a folder from environment variables
244 * \param targetpath: String to return path.
245 * \param subfolder_name: optional name of subfolder within folder.
246 * \param envvar: name of environment variable to check folder_name.
247 * \return true if it was able to construct such a path.
249 static bool get_path_environment(
251 size_t targetpath_len,
252 const char *subfolder_name,
255 char user_path[FILE_MAX];
257 if (test_env_path(user_path, envvar)) {
258 if (subfolder_name) {
267 BLI_strncpy(targetpath, user_path, FILE_MAX);
275 * Returns the path of a folder within the user-files area.
278 * \param targetpath String to return path
279 * \param folder_name default name of folder within user area
280 * \param subfolder_name optional name of subfolder within folder
281 * \param ver Blender version, used to construct a subdirectory name
282 * \return true if it was able to construct such a path.
284 static bool get_path_user(
285 char *targetpath, size_t targetpath_len, const char *folder_name, const char *subfolder_name,
288 char user_path[FILE_MAX];
289 const char *user_base_path;
291 /* for portable install, user path is always local */
292 if (BKE_appdir_app_is_portable_install()) {
293 return get_path_local(targetpath, targetpath_len, folder_name, subfolder_name, ver);
297 user_base_path = (const char *)GHOST_getUserDir(ver, blender_version_decimal(ver));
299 BLI_strncpy(user_path, user_base_path, FILE_MAX);
305 printf("%s: %s\n", __func__, user_path);
308 if (subfolder_name) {
309 return test_path(targetpath, targetpath_len, user_path, folder_name, subfolder_name);
312 return test_path(targetpath, targetpath_len, user_path, NULL, folder_name);
317 * Returns the path of a folder within the Blender installation directory.
319 * \param targetpath String to return path
320 * \param folder_name default name of folder within installation area
321 * \param subfolder_name optional name of subfolder within folder
322 * \param ver Blender version, used to construct a subdirectory name
323 * \return true if it was able to construct such a path.
325 static bool get_path_system(
326 char *targetpath, size_t targetpath_len, const char *folder_name, const char *subfolder_name,
329 char system_path[FILE_MAX];
330 const char *system_base_path;
331 char relfolder[FILE_MAX];
334 if (subfolder_name) {
335 BLI_join_dirfile(relfolder, sizeof(relfolder), folder_name, subfolder_name);
338 BLI_strncpy(relfolder, folder_name, sizeof(relfolder));
345 system_path[0] = '\0';
346 system_base_path = (const char *)GHOST_getSystemDir(ver, blender_version_decimal(ver));
347 if (system_base_path)
348 BLI_strncpy(system_path, system_base_path, FILE_MAX);
354 printf("%s: %s\n", __func__, system_path);
357 if (subfolder_name) {
358 /* try $BLENDERPATH/folder_name/subfolder_name */
359 return test_path(targetpath, targetpath_len, system_path, folder_name, subfolder_name);
362 /* try $BLENDERPATH/folder_name */
363 return test_path(targetpath, targetpath_len, system_path, NULL, folder_name);
368 * Get a folder out of the 'folder_id' presets for paths.
369 * returns the path if found, NULL string if not
371 * \param subfolder: The name of a directory to check for,
372 * this may contain path separators but must resolve to a directory, checked with #BLI_is_dir.
374 const char *BKE_appdir_folder_id_ex(
375 const int folder_id, const char *subfolder,
376 char *path, size_t path_len)
378 const int ver = BLENDER_VERSION;
381 case BLENDER_DATAFILES: /* general case */
382 if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) break;
383 if (get_path_user(path, path_len, "datafiles", subfolder, ver)) break;
384 if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_DATAFILES")) break;
385 if (get_path_local(path, path_len, "datafiles", subfolder, ver)) break;
386 if (get_path_system(path, path_len, "datafiles", subfolder, ver)) break;
389 case BLENDER_USER_DATAFILES:
390 if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) break;
391 if (get_path_user(path, path_len, "datafiles", subfolder, ver)) break;
394 case BLENDER_SYSTEM_DATAFILES:
395 if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_DATAFILES")) break;
396 if (get_path_system(path, path_len, "datafiles", subfolder, ver)) break;
397 if (get_path_local(path, path_len, "datafiles", subfolder, ver)) break;
400 case BLENDER_USER_AUTOSAVE:
401 if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) break;
402 if (get_path_user(path, path_len, "autosave", subfolder, ver)) break;
405 case BLENDER_USER_CONFIG:
406 if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_CONFIG")) break;
407 if (get_path_user(path, path_len, "config", subfolder, ver)) break;
410 case BLENDER_USER_SCRIPTS:
411 if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_SCRIPTS")) break;
412 if (get_path_user(path, path_len, "scripts", subfolder, ver)) break;
415 case BLENDER_SYSTEM_SCRIPTS:
416 if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_SCRIPTS")) break;
417 if (get_path_system(path, path_len, "scripts", subfolder, ver)) break;
418 if (get_path_local(path, path_len, "scripts", subfolder, ver)) break;
421 case BLENDER_SYSTEM_PYTHON:
422 if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_PYTHON")) break;
423 if (get_path_system(path, path_len, "python", subfolder, ver)) break;
424 if (get_path_local(path, path_len, "python", subfolder, ver)) break;
435 const char *BKE_appdir_folder_id(
436 const int folder_id, const char *subfolder)
438 static char path[FILE_MAX] = "";
439 return BKE_appdir_folder_id_ex(folder_id, subfolder, path, sizeof(path));
443 * Returns the path to a folder in the user area without checking that it actually exists first.
445 const char *BKE_appdir_folder_id_user_notest(const int folder_id, const char *subfolder)
447 const int ver = BLENDER_VERSION;
448 static char path[FILE_MAX] = "";
451 case BLENDER_USER_DATAFILES:
452 if (get_path_environment(path, sizeof(path), subfolder, "BLENDER_USER_DATAFILES")) break;
453 get_path_user(path, sizeof(path), "datafiles", subfolder, ver);
455 case BLENDER_USER_CONFIG:
456 if (get_path_environment(path, sizeof(path), subfolder, "BLENDER_USER_CONFIG")) break;
457 get_path_user(path, sizeof(path), "config", subfolder, ver);
459 case BLENDER_USER_AUTOSAVE:
460 if (get_path_environment(path, sizeof(path), subfolder, "BLENDER_USER_AUTOSAVE")) break;
461 get_path_user(path, sizeof(path), "autosave", subfolder, ver);
463 case BLENDER_USER_SCRIPTS:
464 if (get_path_environment(path, sizeof(path), subfolder, "BLENDER_USER_SCRIPTS")) break;
465 get_path_user(path, sizeof(path), "scripts", subfolder, ver);
472 if ('\0' == path[0]) {
479 * Returns the path to a folder in the user area, creating it if it doesn't exist.
481 const char *BKE_appdir_folder_id_create(const int folder_id, const char *subfolder)
485 /* only for user folders */
486 if (!ELEM(folder_id, BLENDER_USER_DATAFILES, BLENDER_USER_CONFIG, BLENDER_USER_SCRIPTS, BLENDER_USER_AUTOSAVE))
489 path = BKE_appdir_folder_id(folder_id, subfolder);
492 path = BKE_appdir_folder_id_user_notest(folder_id, subfolder);
493 if (path) BLI_dir_create_recursive(path);
500 * Returns the path of the top-level version-specific local, user or system directory.
501 * If do_check, then the result will be NULL if the directory doesn't exist.
503 const char *BKE_appdir_folder_id_version(const int folder_id, const int ver, const bool do_check)
505 static char path[FILE_MAX] = "";
508 case BLENDER_RESOURCE_PATH_USER:
509 ok = get_path_user(path, sizeof(path), NULL, NULL, ver);
511 case BLENDER_RESOURCE_PATH_LOCAL:
512 ok = get_path_local(path, sizeof(path), NULL, NULL, ver);
514 case BLENDER_RESOURCE_PATH_SYSTEM:
515 ok = get_path_system(path, sizeof(path), NULL, NULL, ver);
518 path[0] = '\0'; /* in case do_check is false */
520 BLI_assert(!"incorrect ID");
524 if (!ok && do_check) {
536 /* -------------------------------------------------------------------- */
540 * Checks if name is a fully qualified filename to an executable.
541 * If not it searches $PATH for the file. On Windows it also
542 * adds the correct extension (.com .exe etc) from
543 * $PATHEXT if necessary. Also on Windows it translates
544 * the name to its 8.3 version to prevent problems with
545 * spaces and stuff. Final result is returned in fullname.
547 * \param fullname The full path and full name of the executable
548 * (must be FILE_MAX minimum)
549 * \param name The name of the executable (usually argv[0]) to be checked
551 static void where_am_i(char *fullname, const size_t maxlen, const char *name)
554 /* linux uses binreloc since argv[0] is not reliable, call br_init( NULL ) first */
556 const char *path = NULL;
557 path = br_find_exe(NULL);
559 BLI_strncpy(fullname, path, maxlen);
568 wchar_t *fullname_16 = MEM_mallocN(maxlen * sizeof(wchar_t), "ProgramPath");
569 if (GetModuleFileNameW(0, fullname_16, maxlen)) {
570 conv_utf_16_to_8(fullname_16, fullname, maxlen);
571 if (!BLI_exists(fullname)) {
572 printf("path can't be found: \"%.*s\"\n", (int)maxlen, fullname);
573 MessageBox(NULL, "path contains invalid characters or is too long (see console)", "Error", MB_OK);
575 MEM_freeN(fullname_16);
579 MEM_freeN(fullname_16);
583 /* unix and non linux */
584 if (name && name[0]) {
586 BLI_strncpy(fullname, name, maxlen);
587 if (name[0] == '.') {
588 BLI_path_cwd(fullname, maxlen);
590 BLI_path_program_extensions_add_win32(fullname, maxlen);
593 else if (BLI_last_slash(name)) {
595 BLI_strncpy(fullname, name, maxlen);
597 BLI_path_program_extensions_add_win32(fullname, maxlen);
601 BLI_path_program_search(fullname, maxlen, name);
603 /* Remove "/./" and "/../" so string comparisons can be used on the path. */
604 BLI_cleanup_path(NULL, fullname);
607 if (!STREQ(name, fullname)) {
608 printf("guessing '%s' == '%s'\n", name, fullname);
614 void BKE_appdir_program_path_init(const char *argv0)
616 where_am_i(bprogname, sizeof(bprogname), argv0);
617 BLI_split_dir_part(bprogname, bprogdir, sizeof(bprogdir));
623 const char *BKE_appdir_program_path(void)
629 * Path to directory of executable
631 const char *BKE_appdir_program_dir(void)
636 bool BKE_appdir_program_python_search(
637 char *fullpath, const size_t fullpath_len,
638 const int version_major, const int version_minor)
640 #ifdef PYTHON_EXECUTABLE_NAME
641 /* passed in from the build-systems 'PYTHON_EXECUTABLE' */
642 const char *python_build_def = STRINGIFY(PYTHON_EXECUTABLE_NAME);
644 const char *basename = "python";
646 /* check both possible names */
647 const char *python_names[] = {
648 #ifdef PYTHON_EXECUTABLE_NAME
656 bool is_found = false;
658 BLI_snprintf(python_ver, sizeof(python_ver), "%s%d.%d", basename, version_major, version_minor);
661 const char *python_bin_dir = BKE_appdir_folder_id(BLENDER_SYSTEM_PYTHON, "bin");
662 if (python_bin_dir) {
664 for (i = 0; i < ARRAY_SIZE(python_names); i++) {
665 BLI_join_dirfile(fullpath, fullpath_len, python_bin_dir, python_names[i]);
669 BLI_path_program_extensions_add_win32(fullpath, fullpath_len)
682 if (is_found == false) {
683 for (i = 0; i < ARRAY_SIZE(python_names); i++) {
684 if (BLI_path_program_search(fullpath, fullpath_len, python_names[i])) {
691 if (is_found == false) {
698 /** Keep in sync with `bpy.utils.app_template_paths()` */
699 static const char *app_template_directory_search[2] = {
700 "startup" SEP_STR "bl_app_templates_user",
701 "startup" SEP_STR "bl_app_templates_system",
704 static const int app_template_directory_id[2] = {
706 BLENDER_USER_SCRIPTS,
707 /* Covers 'LOCAL' & 'SYSTEM'. */
708 BLENDER_SYSTEM_SCRIPTS,
712 * Return true if templates exist
714 bool BKE_appdir_app_template_any(void)
716 char temp_dir[FILE_MAX];
717 for (int i = 0; i < 2; i++) {
718 if (BKE_appdir_folder_id_ex(
719 app_template_directory_id[i], app_template_directory_search[i],
720 temp_dir, sizeof(temp_dir)))
728 bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_len)
730 for (int i = 0; i < 2; i++) {
731 char subdir[FILE_MAX];
732 BLI_join_dirfile(subdir, sizeof(subdir), app_template_directory_search[i], app_template);
733 if (BKE_appdir_folder_id_ex(
734 app_template_directory_id[i], subdir,
743 void BKE_appdir_app_templates(ListBase *templates)
745 BLI_listbase_clear(templates);
747 for (int i = 0; i < 2; i++) {
748 char subdir[FILE_MAX];
749 if (!BKE_appdir_folder_id_ex(
750 app_template_directory_id[i], app_template_directory_search[i],
751 subdir, sizeof(subdir)))
756 struct direntry *dir;
757 uint totfile = BLI_filelist_dir_contents(subdir, &dir);
758 for (int f = 0; f < totfile; f++) {
759 if (!FILENAME_IS_CURRPAR(dir[f].relname) && S_ISDIR(dir[f].type)) {
760 char *template = BLI_strdup(dir[f].relname);
761 BLI_addtail(templates, BLI_genericNodeN(template));
765 BLI_filelist_free(dir, totfile);
770 * Gets the temp directory when blender first runs.
771 * If the default path is not found, use try $TEMP
773 * Also make sure the temp dir has a trailing slash
775 * \param fullname The full path to the temporary temp directory
776 * \param basename The full path to the persistent temp directory (may be NULL)
777 * \param maxlen The size of the fullname buffer
778 * \param userdir Directory specified in user preferences
780 static void where_is_temp(char *fullname, char *basename, const size_t maxlen, char *userdir)
782 /* Clear existing temp dir, if needed. */
783 BKE_tempdir_session_purge();
790 if (userdir && BLI_is_dir(userdir)) {
791 BLI_strncpy(fullname, userdir, maxlen);
796 if (fullname[0] == '\0') {
797 const char *tmp = BLI_getenv("TEMP"); /* Windows */
798 if (tmp && BLI_is_dir(tmp)) {
799 BLI_strncpy(fullname, tmp, maxlen);
803 /* Other OS's - Try TMP and TMPDIR */
804 if (fullname[0] == '\0') {
805 const char *tmp = BLI_getenv("TMP");
806 if (tmp && BLI_is_dir(tmp)) {
807 BLI_strncpy(fullname, tmp, maxlen);
811 if (fullname[0] == '\0') {
812 const char *tmp = BLI_getenv("TMPDIR");
813 if (tmp && BLI_is_dir(tmp)) {
814 BLI_strncpy(fullname, tmp, maxlen);
819 if (fullname[0] == '\0') {
820 BLI_strncpy(fullname, "/tmp/", maxlen);
823 /* add a trailing slash if needed */
824 BLI_add_slash(fullname);
826 if (userdir && userdir != fullname) {
827 /* also set user pref to show %TEMP%. /tmp/ is just plain confusing for Windows users. */
828 BLI_strncpy(userdir, fullname, maxlen);
833 /* Now that we have a valid temp dir, add system-generated unique sub-dir. */
835 /* 'XXXXXX' is kind of tag to be replaced by mktemp-familly by an uuid. */
836 char *tmp_name = BLI_strdupcat(fullname, "blender_XXXXXX");
837 const size_t ln = strlen(tmp_name) + 1;
840 if (_mktemp_s(tmp_name, ln) == 0) {
841 BLI_dir_create_recursive(tmp_name);
844 if (mkdtemp(tmp_name) == NULL) {
845 BLI_dir_create_recursive(tmp_name);
849 if (BLI_is_dir(tmp_name)) {
850 BLI_strncpy(basename, fullname, maxlen);
851 BLI_strncpy(fullname, tmp_name, maxlen);
852 BLI_add_slash(fullname);
855 printf("Warning! Could not generate a temp file name for '%s', falling back to '%s'\n", tmp_name, fullname);
863 * Sets btempdir_base to userdir if specified and is a valid directory, otherwise
864 * chooses a suitable OS-specific temporary directory.
865 * Sets btempdir_session to a mkdtemp-generated sub-dir of btempdir_base.
867 * \note On Window userdir will be set to the temporary directory!
869 void BKE_tempdir_init(char *userdir)
871 where_is_temp(btempdir_session, btempdir_base, FILE_MAX, userdir);
875 * Path to temporary directory (with trailing slash)
877 const char *BKE_tempdir_session(void)
879 return btempdir_session[0] ? btempdir_session : BKE_tempdir_base();
883 * Path to persistent temporary directory (with trailing slash)
885 const char *BKE_tempdir_base(void)
887 return btempdir_base;
891 * Path to the system temporary directory (with trailing slash)
893 void BKE_tempdir_system_init(char *dir)
895 where_is_temp(dir, NULL, FILE_MAX, NULL);
899 * Delete content of this instance's temp dir.
901 void BKE_tempdir_session_purge(void)
903 if (btempdir_session[0] && BLI_is_dir(btempdir_session)) {
904 BLI_delete(btempdir_session, true, true);