From 1b3e4838991aa2431266b8f0240f207a9f508b6b Mon Sep 17 00:00:00 2001 From: Adam Scott Date: Mon, 21 Oct 2024 15:02:08 -0400 Subject: [PATCH] Add file and dir temporary utilities Co-authored by @Alex2782 for the Android bindings. Many thanks to the reviewers also. Co-authored-by: Alex --- core/core_bind.cpp | 6 ++ core/core_bind.h | 1 + core/io/dir_access.cpp | 81 ++++++++++++++++++- core/io/dir_access.h | 11 ++- core/io/file_access.cpp | 79 ++++++++++++++++++ core/io/file_access.h | 11 ++- core/os/os.cpp | 4 + core/os/os.h | 1 + doc/classes/DirAccess.xml | 11 +++ doc/classes/FileAccess.xml | 14 ++++ doc/classes/OS.xml | 6 ++ drivers/unix/os_unix.cpp | 4 + drivers/unix/os_unix.h | 2 + editor/editor_paths.cpp | 6 ++ editor/editor_paths.h | 2 + .../src/org/godotengine/godot/GodotIO.java | 12 +++ platform/android/java_godot_io_wrapper.cpp | 12 +++ platform/android/java_godot_io_wrapper.h | 2 + platform/android/os_android.cpp | 13 +++ platform/android/os_android.h | 2 + platform/ios/os_ios.h | 1 + platform/ios/os_ios.mm | 13 +++ platform/macos/os_macos.h | 1 + platform/macos/os_macos.mm | 13 +++ platform/windows/os_windows.cpp | 29 ++++++- platform/windows/os_windows.h | 1 + 26 files changed, 331 insertions(+), 7 deletions(-) diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 925551d933f..66bf5f41e14 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -577,6 +577,11 @@ String OS::get_cache_dir() const { return ::OS::get_singleton()->get_cache_path(); } +String OS::get_temp_dir() const { + // Exposed as `get_temp_dir()` instead of `get_temp_path()` for consistency with other exposed OS methods. + return ::OS::get_singleton()->get_temp_path(); +} + bool OS::is_debug_build() const { #ifdef DEBUG_ENABLED return true; @@ -705,6 +710,7 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_config_dir"), &OS::get_config_dir); ClassDB::bind_method(D_METHOD("get_data_dir"), &OS::get_data_dir); ClassDB::bind_method(D_METHOD("get_cache_dir"), &OS::get_cache_dir); + ClassDB::bind_method(D_METHOD("get_temp_dir"), &OS::get_temp_dir); ClassDB::bind_method(D_METHOD("get_unique_id"), &OS::get_unique_id); ClassDB::bind_method(D_METHOD("get_keycode_string", "code"), &OS::get_keycode_string); diff --git a/core/core_bind.h b/core/core_bind.h index d013e348bd8..3b0442bd65a 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -262,6 +262,7 @@ public: String get_config_dir() const; String get_data_dir() const; String get_cache_dir() const; + String get_temp_dir() const; Error set_thread_name(const String &p_name); ::Thread::ID get_thread_caller_id() const; diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 14588923cb3..c2fb4b9cb2a 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -32,8 +32,8 @@ #include "core/config/project_settings.h" #include "core/io/file_access.h" -#include "core/os/memory.h" #include "core/os/os.h" +#include "core/os/time.h" #include "core/templates/local_vector.h" thread_local Error DirAccess::last_dir_open_error = OK; @@ -323,6 +323,80 @@ Ref DirAccess::create(AccessType p_access) { return da; } +Ref DirAccess::create_temp(const String &p_prefix, bool p_keep, Error *r_error) { + const String ERROR_COMMON_PREFIX = "Error while creating temporary directory"; + + if (!p_prefix.is_valid_filename()) { + *r_error = ERR_FILE_BAD_PATH; + ERR_FAIL_V_MSG(Ref(), vformat(R"(%s: "%s" is not a valid prefix.)", ERROR_COMMON_PREFIX, p_prefix)); + } + + Ref dir_access = DirAccess::open(OS::get_singleton()->get_temp_path()); + + uint32_t suffix_i = 0; + String path; + while (true) { + String datetime = Time::get_singleton()->get_datetime_string_from_system().replace("-", "").replace("T", "").replace(":", ""); + datetime += itos(Time::get_singleton()->get_ticks_usec()); + String suffix = datetime + (suffix_i > 0 ? itos(suffix_i) : ""); + path = (p_prefix.is_empty() ? "" : p_prefix + "-") + suffix; + if (!path.is_valid_filename()) { + *r_error = ERR_FILE_BAD_PATH; + return Ref(); + } + if (!DirAccess::exists(path)) { + break; + } + suffix_i += 1; + } + + Error err = dir_access->make_dir(path); + if (err != OK) { + *r_error = err; + ERR_FAIL_V_MSG(Ref(), vformat(R"(%s: "%s" couldn't create directory "%s".)", ERROR_COMMON_PREFIX, path)); + } + err = dir_access->change_dir(path); + if (err != OK) { + *r_error = err; + return Ref(); + } + + dir_access->_is_temp = true; + dir_access->_temp_keep_after_free = p_keep; + dir_access->_temp_path = dir_access->get_current_dir(); + + *r_error = OK; + return dir_access; +} + +Ref DirAccess::_create_temp(const String &p_prefix, bool p_keep) { + return create_temp(p_prefix, p_keep, &last_dir_open_error); +} + +void DirAccess::_delete_temp() { + if (!_is_temp || _temp_keep_after_free) { + return; + } + + if (!DirAccess::exists(_temp_path)) { + return; + } + + Error err; + { + Ref dir_access = DirAccess::open(_temp_path, &err); + if (err != OK) { + return; + } + err = dir_access->erase_contents_recursive(); + if (err != OK) { + return; + } + } + + DirAccess::remove_absolute(_temp_path); +} + Error DirAccess::get_open_error() { return last_dir_open_error; } @@ -555,6 +629,7 @@ bool DirAccess::is_case_sensitive(const String &p_path) const { void DirAccess::_bind_methods() { ClassDB::bind_static_method("DirAccess", D_METHOD("open", "path"), &DirAccess::_open); ClassDB::bind_static_method("DirAccess", D_METHOD("get_open_error"), &DirAccess::get_open_error); + ClassDB::bind_static_method("DirAccess", D_METHOD("create_temp", "prefix", "keep"), &DirAccess::_create_temp, DEFVAL(""), DEFVAL(false)); ClassDB::bind_method(D_METHOD("list_dir_begin"), &DirAccess::list_dir_begin, DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_next"), &DirAccess::_get_next); @@ -598,3 +673,7 @@ void DirAccess::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_navigational"), "set_include_navigational", "get_include_navigational"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_hidden"), "set_include_hidden", "get_include_hidden"); } + +DirAccess::~DirAccess() { + _delete_temp(); +} diff --git a/core/io/dir_access.h b/core/io/dir_access.h index 2392944f761..21baac9e656 100644 --- a/core/io/dir_access.h +++ b/core/io/dir_access.h @@ -61,6 +61,13 @@ private: bool include_navigational = false; bool include_hidden = false; + bool _is_temp = false; + bool _temp_keep_after_free = false; + String _temp_path; + void _delete_temp(); + + static Ref _create_temp(const String &p_prefix = "", bool p_keep = false); + protected: static void _bind_methods(); @@ -136,6 +143,7 @@ public: } static Ref open(const String &p_path, Error *r_error = nullptr); + static Ref create_temp(const String &p_prefix = "", bool p_keep = false, Error *r_error = nullptr); static int _get_drive_count(); static String get_drive_name(int p_idx); @@ -161,8 +169,9 @@ public: virtual bool is_case_sensitive(const String &p_path) const; +public: DirAccess() {} - virtual ~DirAccess() {} + virtual ~DirAccess(); }; #endif // DIR_ACCESS_H diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index 29027cade11..de3fe286e04 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -38,6 +38,7 @@ #include "core/io/file_access_pack.h" #include "core/io/marshalls.h" #include "core/os/os.h" +#include "core/os/time.h" FileAccess::CreateFunc FileAccess::create_func[ACCESS_MAX] = {}; @@ -84,6 +85,79 @@ Ref FileAccess::create_for_path(const String &p_path) { return ret; } +Ref FileAccess::create_temp(int p_mode_flags, const String &p_prefix, const String &p_extension, bool p_keep, Error *r_error) { + const String ERROR_COMMON_PREFIX = "Error while creating temporary file"; + + if (!p_prefix.is_valid_filename()) { + *r_error = ERR_FILE_BAD_PATH; + ERR_FAIL_V_MSG(Ref(), vformat(R"(%s: "%s" is not a valid prefix.)", ERROR_COMMON_PREFIX, p_prefix)); + } + + if (!p_extension.is_valid_filename()) { + *r_error = ERR_FILE_BAD_PATH; + ERR_FAIL_V_MSG(Ref(), vformat(R"(%s: "%s" is not a valid extension.)", ERROR_COMMON_PREFIX, p_extension)); + } + + const String TEMP_DIR = OS::get_singleton()->get_temp_path(); + String extension = p_extension.trim_prefix("."); + + uint32_t suffix_i = 0; + String path; + while (true) { + String datetime = Time::get_singleton()->get_datetime_string_from_system().replace("-", "").replace("T", "").replace(":", ""); + datetime += itos(Time::get_singleton()->get_ticks_usec()); + String suffix = datetime + (suffix_i > 0 ? itos(suffix_i) : ""); + path = TEMP_DIR.path_join((p_prefix.is_empty() ? "" : p_prefix + "-") + suffix + (extension.is_empty() ? "" : "." + extension)); + if (!DirAccess::exists(path)) { + break; + } + suffix_i += 1; + } + + Error err; + { + // Create file first with WRITE mode. + // Otherwise, it would fail to open with a READ mode. + Ref ret = FileAccess::open(path, FileAccess::ModeFlags::WRITE, &err); + if (err != OK) { + *r_error = err; + ERR_FAIL_V_MSG(Ref(), vformat(R"(%s: could not create "%s".)", ERROR_COMMON_PREFIX, path)); + } + ret->flush(); + } + + // Open then the temp file with the correct mode flag. + Ref ret = FileAccess::open(path, p_mode_flags, &err); + if (err != OK) { + *r_error = err; + ERR_FAIL_V_MSG(Ref(), vformat(R"(%s: could not open "%s".)", ERROR_COMMON_PREFIX, path)); + } + if (ret.is_valid()) { + ret->_is_temp_file = true; + ret->_temp_keep_after_use = p_keep; + ret->_temp_path = ret->get_path_absolute(); + } + + *r_error = OK; + return ret; +} + +Ref FileAccess::_create_temp(int p_mode_flags, const String &p_prefix, const String &p_extension, bool p_keep) { + return create_temp(p_mode_flags, p_prefix, p_extension, p_keep, &last_file_open_error); +} + +void FileAccess::_delete_temp() { + if (!_is_temp_file || _temp_keep_after_use) { + return; + } + + if (!FileAccess::exists(_temp_path)) { + return; + } + + DirAccess::remove_absolute(_temp_path); +} + Error FileAccess::reopen(const String &p_path, int p_mode_flags) { return open_internal(p_path, p_mode_flags); } @@ -823,6 +897,7 @@ void FileAccess::_bind_methods() { ClassDB::bind_static_method("FileAccess", D_METHOD("open_encrypted_with_pass", "path", "mode_flags", "pass"), &FileAccess::open_encrypted_pass); ClassDB::bind_static_method("FileAccess", D_METHOD("open_compressed", "path", "mode_flags", "compression_mode"), &FileAccess::open_compressed, DEFVAL(0)); ClassDB::bind_static_method("FileAccess", D_METHOD("get_open_error"), &FileAccess::get_open_error); + ClassDB::bind_static_method("FileAccess", D_METHOD("create_temp", "mode_flags", "prefix", "extension", "keep"), &FileAccess::_create_temp, DEFVAL(""), DEFVAL(""), DEFVAL(false)); ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_bytes", "path"), &FileAccess::_get_file_as_bytes); ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_string", "path"), &FileAccess::_get_file_as_string); @@ -912,3 +987,7 @@ void FileAccess::_bind_methods() { BIND_BITFIELD_FLAG(UNIX_SET_GROUP_ID); BIND_BITFIELD_FLAG(UNIX_RESTRICTED_DELETE); } + +FileAccess::~FileAccess() { + _delete_temp(); +} diff --git a/core/io/file_access.h b/core/io/file_access.h index 48984c6c1bf..b781c0f7dd0 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -128,6 +128,13 @@ private: static Ref _open(const String &p_path, ModeFlags p_mode_flags); + bool _is_temp_file = false; + bool _temp_keep_after_use = false; + String _temp_path; + void _delete_temp(); + + static Ref _create_temp(int p_mode_flags, const String &p_prefix = "", const String &p_extension = "", bool p_keep = false); + public: static void set_file_close_fail_notify_callback(FileCloseFailNotify p_cbk) { close_fail_notify = p_cbk; } @@ -206,6 +213,7 @@ public: static Ref create(AccessType p_access); /// Create a file access (for the current platform) this is the only portable way of accessing files. static Ref create_for_path(const String &p_path); static Ref open(const String &p_path, int p_mode_flags, Error *r_error = nullptr); /// Create a file access (for the current platform) this is the only portable way of accessing files. + static Ref create_temp(int p_mode_flags, const String &p_prefix = "", const String &p_extension = "", bool p_keep = false, Error *r_error = nullptr); static Ref open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector &p_key, const Vector &p_iv = Vector()); static Ref open_encrypted_pass(const String &p_path, ModeFlags p_mode_flags, const String &p_pass); @@ -241,8 +249,9 @@ public: create_func[p_access] = _create_builtin; } +public: FileAccess() {} - virtual ~FileAccess() {} + virtual ~FileAccess(); }; VARIANT_ENUM_CAST(FileAccess::CompressionMode); diff --git a/core/os/os.cpp b/core/os/os.cpp index 59a0579ce36..4a833645f06 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -276,6 +276,10 @@ String OS::get_cache_path() const { return "."; } +String OS::get_temp_path() const { + return "."; +} + // Path to macOS .app bundle resources String OS::get_bundle_resource_dir() const { return "."; diff --git a/core/os/os.h b/core/os/os.h index ffdb905abaf..90135d318e4 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -287,6 +287,7 @@ public: virtual String get_data_path() const; virtual String get_config_path() const; virtual String get_cache_path() const; + virtual String get_temp_path() const; virtual String get_bundle_resource_dir() const; virtual String get_bundle_icon_path() const; diff --git a/doc/classes/DirAccess.xml b/doc/classes/DirAccess.xml index 0f5844fd635..eca691303fb 100644 --- a/doc/classes/DirAccess.xml +++ b/doc/classes/DirAccess.xml @@ -105,6 +105,17 @@ [b]Note:[/b] This method is implemented on macOS, Linux, and Windows. + + + + + + Creates a temporary directory. This directory will be freed when the returned [DirAccess] is freed. + If [param prefix] is not empty, it will be prefixed to the directory name, separated by a [code]-[/code]. + If [param keep] is [code]true[/code], the directory is not deleted when the returned [DirAccess] is freed. + Returns [code]null[/code] if opening the directory failed. You can use [method get_open_error] to check the error that occurred. + + diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index 846897a463d..1267c966841 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -50,6 +50,20 @@ [b]Note:[/b] [FileAccess] will automatically close when it's freed, which happens when it goes out of scope or when it gets assigned with [code]null[/code]. In C# the reference must be disposed after we are done using it, this can be done with the [code]using[/code] statement or calling the [code]Dispose[/code] method directly. + + + + + + + + Creates a temporary file. This file will be freed when the returned [FileAccess] is freed. + If [param prefix] is not empty, it will be prefixed to the file name, separated by a [code]-[/code]. + If [param extension] is not empty, it will be appended to the temporary file name. + If [param keep] is [code]true[/code], the file is not deleted when the returned [FileAccess] is freed. + Returns [code]null[/code] if opening the file failed. You can use [method get_open_error] to check the error that occurred. + + diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 2389db1301b..69d613b7ccc 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -537,6 +537,12 @@ [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. + + + + Returns the [i]global[/i] temporary data directory according to the operating system's standards. + + diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 299ac6536f8..5a22434fe33 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -308,6 +308,10 @@ String OS_Unix::get_version() const { return ""; } +String OS_Unix::get_temp_path() const { + return "/tmp"; +} + double OS_Unix::get_unix_time() const { struct timeval tv_now; gettimeofday(&tv_now, nullptr); diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index 2c7920c1426..d331b0fb8e8 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -76,6 +76,8 @@ public: virtual String get_distribution_name() const override; virtual String get_version() const override; + virtual String get_temp_path() const override; + virtual DateTime get_datetime(bool p_utc) const override; virtual TimeZoneInfo get_time_zone_info() const override; diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp index 883116bab68..e48a9f8994a 100644 --- a/editor/editor_paths.cpp +++ b/editor/editor_paths.cpp @@ -54,6 +54,10 @@ String EditorPaths::get_cache_dir() const { return cache_dir; } +String EditorPaths::get_temp_dir() const { + return temp_dir; +} + String EditorPaths::get_project_data_dir() const { return project_data_dir; } @@ -160,6 +164,7 @@ EditorPaths::EditorPaths() { config_dir = data_dir; cache_path = exe_path; cache_dir = data_dir.path_join("cache"); + temp_dir = data_dir.path_join("temp"); } else { // Typically XDG_DATA_HOME or %APPDATA%. data_path = OS::get_singleton()->get_data_path(); @@ -174,6 +179,7 @@ EditorPaths::EditorPaths() { } else { cache_dir = cache_path.path_join(OS::get_singleton()->get_godot_dir_name()); } + temp_dir = OS::get_singleton()->get_temp_path(); } paths_valid = (!data_path.is_empty() && !config_path.is_empty() && !cache_path.is_empty()); diff --git a/editor/editor_paths.h b/editor/editor_paths.h index a396c433018..2fa288727c8 100644 --- a/editor/editor_paths.h +++ b/editor/editor_paths.h @@ -42,6 +42,7 @@ class EditorPaths : public Object { String data_dir; // Editor data (templates, shader cache, etc.). String config_dir; // Editor config (settings, profiles, themes, etc.). String cache_dir; // Editor cache (thumbnails, tmp generated files). + String temp_dir; // Editor temporary directory. String project_data_dir; // Project-specific data (metadata, shader cache, etc.). bool self_contained = false; // Self-contained means everything goes to `editor_data` dir. String self_contained_file; // Self-contained file with configuration. @@ -61,6 +62,7 @@ public: String get_data_dir() const; String get_config_dir() const; String get_cache_dir() const; + String get_temp_dir() const; String get_project_data_dir() const; String get_export_templates_dir() const; String get_debug_keystore_path() const; diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index 79751dd58f3..026bb3f1476 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -132,6 +132,18 @@ public class GodotIO { return activity.getCacheDir().getAbsolutePath(); } + public String getTempDir() { + File tempDir = new File(getCacheDir() + "/tmp"); + + if (!tempDir.exists()) { + if (!tempDir.mkdirs()) { + Log.e(TAG, "Unable to create temp dir"); + } + } + + return tempDir.getAbsolutePath(); + } + public String getDataDir() { return activity.getFilesDir().getAbsolutePath(); } diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 623db39985f..b091fdbd2bd 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -52,6 +52,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc _open_URI = p_env->GetMethodID(cls, "openURI", "(Ljava/lang/String;)I"); _get_cache_dir = p_env->GetMethodID(cls, "getCacheDir", "()Ljava/lang/String;"); + _get_temp_dir = p_env->GetMethodID(cls, "getTempDir", "()Ljava/lang/String;"); _get_data_dir = p_env->GetMethodID(cls, "getDataDir", "()Ljava/lang/String;"); _get_display_cutouts = p_env->GetMethodID(cls, "getDisplayCutouts", "()[I"), _get_display_safe_area = p_env->GetMethodID(cls, "getDisplaySafeArea", "()[I"), @@ -106,6 +107,17 @@ String GodotIOJavaWrapper::get_cache_dir() { } } +String GodotIOJavaWrapper::get_temp_dir() { + if (_get_temp_dir) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, String()); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_temp_dir); + return jstring_to_string(s, env); + } else { + return String(); + } +} + String GodotIOJavaWrapper::get_user_data_dir() { if (_get_data_dir) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index 0a372641cbb..294f0dc9d82 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -48,6 +48,7 @@ private: jmethodID _open_URI = 0; jmethodID _get_cache_dir = 0; jmethodID _get_data_dir = 0; + jmethodID _get_temp_dir = 0; jmethodID _get_display_cutouts = 0; jmethodID _get_display_safe_area = 0; jmethodID _get_locale = 0; @@ -71,6 +72,7 @@ public: Error open_uri(const String &p_uri); String get_cache_dir(); + String get_temp_dir(); String get_user_data_dir(); String get_locale(); String get_model(); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 7b0d3a29e90..3602d6a4622 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -677,6 +677,19 @@ String OS_Android::get_cache_path() const { return "."; } +String OS_Android::get_temp_path() const { + if (!temp_dir_cache.is_empty()) { + return temp_dir_cache; + } + + String temp_dir = godot_io_java->get_temp_dir(); + if (!temp_dir.is_empty()) { + temp_dir_cache = _remove_symlink(temp_dir); + return temp_dir_cache; + } + return "."; +} + String OS_Android::get_unique_id() const { String unique_id = godot_io_java->get_unique_id(); if (!unique_id.is_empty()) { diff --git a/platform/android/os_android.h b/platform/android/os_android.h index fb3cdf0d4c0..ecc5688e5f3 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -58,6 +58,7 @@ private: mutable String data_dir_cache; mutable String cache_dir_cache; + mutable String temp_dir_cache; mutable String remote_fs_dir; AudioDriverOpenSL audio_driver_android; @@ -148,6 +149,7 @@ public: virtual String get_user_data_dir() const override; virtual String get_data_path() const override; virtual String get_cache_path() const override; + virtual String get_temp_path() const override; virtual String get_resource_dir() const override; virtual String get_locale() const override; virtual String get_model_name() const override; diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h index b7c5a730656..e83a54abec2 100644 --- a/platform/ios/os_ios.h +++ b/platform/ios/os_ios.h @@ -117,6 +117,7 @@ public: virtual String get_user_data_dir() const override; virtual String get_cache_path() const override; + virtual String get_temp_path() const override; virtual String get_locale() const override; diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm index 590238be77f..a94eda4e030 100644 --- a/platform/ios/os_ios.mm +++ b/platform/ios/os_ios.mm @@ -336,6 +336,19 @@ String OS_IOS::get_cache_path() const { return ret; } +String OS_IOS::get_temp_path() const { + static String ret; + if (ret.is_empty()) { + NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory() + isDirectory:YES]; + if (url) { + ret = String::utf8([url.path UTF8String]); + ret = ret.trim_prefix("file://"); + } + } + return ret; +} + String OS_IOS::get_locale() const { NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject; diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index 4fb4507837b..939099d3502 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -92,6 +92,7 @@ public: virtual String get_config_path() const override; virtual String get_data_path() const override; virtual String get_cache_path() const override; + virtual String get_temp_path() const override; virtual String get_bundle_resource_dir() const override; virtual String get_bundle_icon_path() const override; virtual String get_godot_dir_name() const override; diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 08ff391aab7..2cee684678f 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -276,6 +276,19 @@ String OS_MacOS::get_cache_path() const { return get_config_path(); } +String OS_MacOS::get_temp_path() const { + static String ret; + if (ret.is_empty()) { + NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory() + isDirectory:YES]; + if (url) { + ret = String::utf8([url.path UTF8String]); + ret = ret.trim_prefix("file://"); + } + } + return ret; +} + String OS_MacOS::get_bundle_resource_dir() const { String ret; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 984c9ae90e3..27a9ad72660 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -2068,16 +2068,37 @@ String OS_Windows::get_cache_path() const { if (has_environment("LOCALAPPDATA")) { cache_path_cache = get_environment("LOCALAPPDATA").replace("\\", "/"); } - if (cache_path_cache.is_empty() && has_environment("TEMP")) { - cache_path_cache = get_environment("TEMP").replace("\\", "/"); - } if (cache_path_cache.is_empty()) { - cache_path_cache = get_config_path(); + cache_path_cache = get_temp_path(); } } return cache_path_cache; } +String OS_Windows::get_temp_path() const { + static String temp_path_cache; + if (temp_path_cache.is_empty()) { + { + Vector temp_path; + // The maximum possible size is MAX_PATH+1 (261) + terminating null character. + temp_path.resize(MAX_PATH + 2); + DWORD temp_path_length = GetTempPathW(temp_path.size(), temp_path.ptrw()); + if (temp_path_length > 0 && temp_path_length < temp_path.size()) { + temp_path_cache = String::utf16((const char16_t *)temp_path.ptr()); + // Let's try to get the long path instead of the short path (with tildes ~). + DWORD temp_path_long_length = GetLongPathNameW(temp_path.ptr(), temp_path.ptrw(), temp_path.size()); + if (temp_path_long_length > 0 && temp_path_long_length < temp_path.size()) { + temp_path_cache = String::utf16((const char16_t *)temp_path.ptr()); + } + } + } + if (temp_path_cache.is_empty()) { + temp_path_cache = get_config_path(); + } + } + return temp_path_cache; +} + // Get properly capitalized engine name for system paths String OS_Windows::get_godot_dir_name() const { return String(VERSION_SHORT_NAME).capitalize(); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index fd98f9b14bd..6a17fc1f60a 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -223,6 +223,7 @@ public: virtual String get_config_path() const override; virtual String get_data_path() const override; virtual String get_cache_path() const override; + virtual String get_temp_path() const override; virtual String get_godot_dir_name() const override; virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;