Forráskód Böngészése

Add file and dir temporary utilities

Co-authored by @Alex2782 for the Android bindings.
Many thanks to the reviewers also.

Co-authored-by: Alex <[email protected]>
Adam Scott 9 hónapja
szülő
commit
1b3e483899

+ 6 - 0
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);

+ 1 - 0
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;

+ 80 - 1
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> DirAccess::create(AccessType p_access) {
 	return da;
 }
 
+Ref<DirAccess> 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<FileAccess>(), vformat(R"(%s: "%s" is not a valid prefix.)", ERROR_COMMON_PREFIX, p_prefix));
+	}
+
+	Ref<DirAccess> 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<DirAccess>();
+		}
+		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<FileAccess>(), 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<DirAccess>();
+	}
+
+	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> 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<DirAccess> 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();
+}

+ 10 - 1
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<DirAccess> _create_temp(const String &p_prefix = "", bool p_keep = false);
+
 protected:
 	static void _bind_methods();
 
@@ -136,6 +143,7 @@ public:
 	}
 
 	static Ref<DirAccess> open(const String &p_path, Error *r_error = nullptr);
+	static Ref<DirAccess> 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

+ 79 - 0
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> FileAccess::create_for_path(const String &p_path) {
 	return ret;
 }
 
+Ref<FileAccess> 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<FileAccess>(), 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<FileAccess>(), 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<FileAccess> ret = FileAccess::open(path, FileAccess::ModeFlags::WRITE, &err);
+		if (err != OK) {
+			*r_error = err;
+			ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: could not create "%s".)", ERROR_COMMON_PREFIX, path));
+		}
+		ret->flush();
+	}
+
+	// Open then the temp file with the correct mode flag.
+	Ref<FileAccess> ret = FileAccess::open(path, p_mode_flags, &err);
+	if (err != OK) {
+		*r_error = err;
+		ERR_FAIL_V_MSG(Ref<FileAccess>(), 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> 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();
+}

+ 10 - 1
core/io/file_access.h

@@ -128,6 +128,13 @@ private:
 
 	static Ref<FileAccess> _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<FileAccess> _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<FileAccess> create(AccessType p_access); /// Create a file access (for the current platform) this is the only portable way of accessing files.
 	static Ref<FileAccess> create_for_path(const String &p_path);
 	static Ref<FileAccess> 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<FileAccess> create_temp(int p_mode_flags, const String &p_prefix = "", const String &p_extension = "", bool p_keep = false, Error *r_error = nullptr);
 
 	static Ref<FileAccess> open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key, const Vector<uint8_t> &p_iv = Vector<uint8_t>());
 	static Ref<FileAccess> 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<T>;
 	}
 
+public:
 	FileAccess() {}
-	virtual ~FileAccess() {}
+	virtual ~FileAccess();
 };
 
 VARIANT_ENUM_CAST(FileAccess::CompressionMode);

+ 4 - 0
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 ".";

+ 1 - 0
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;
 

+ 11 - 0
doc/classes/DirAccess.xml

@@ -105,6 +105,17 @@
 				[b]Note:[/b] This method is implemented on macOS, Linux, and Windows.
 			</description>
 		</method>
+		<method name="create_temp" qualifiers="static">
+			<return type="DirAccess" />
+			<param index="0" name="prefix" type="String" default="&quot;&quot;" />
+			<param index="1" name="keep" type="bool" default="false" />
+			<description>
+				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.
+			</description>
+		</method>
 		<method name="current_is_dir" qualifiers="const">
 			<return type="bool" />
 			<description>

+ 14 - 0
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.
 			</description>
 		</method>
+		<method name="create_temp" qualifiers="static">
+			<return type="FileAccess" />
+			<param index="0" name="mode_flags" type="int" />
+			<param index="1" name="prefix" type="String" default="&quot;&quot;" />
+			<param index="2" name="extension" type="String" default="&quot;&quot;" />
+			<param index="3" name="keep" type="bool" default="false" />
+			<description>
+				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.
+			</description>
+		</method>
 		<method name="eof_reached" qualifiers="const">
 			<return type="bool" />
 			<description>

+ 6 - 0
doc/classes/OS.xml

@@ -537,6 +537,12 @@
 				[b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows.
 			</description>
 		</method>
+		<method name="get_temp_dir" qualifiers="const">
+			<return type="String" />
+			<description>
+				Returns the [i]global[/i] temporary data directory according to the operating system's standards.
+			</description>
+		</method>
 		<method name="get_thread_caller_id" qualifiers="const">
 			<return type="int" />
 			<description>

+ 4 - 0
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);

+ 2 - 0
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;
 

+ 6 - 0
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());

+ 2 - 0
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;

+ 12 - 0
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();
 	}

+ 12 - 0
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();

+ 2 - 0
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();

+ 13 - 0
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()) {

+ 2 - 0
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;

+ 1 - 0
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;
 

+ 13 - 0
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;
 

+ 1 - 0
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;

+ 13 - 0
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;
 

+ 25 - 4
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<WCHAR> 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();

+ 1 - 0
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;