Skip to content

Commit 1b3e483

Browse files
adamscottAlex2782
andcommitted
Add file and dir temporary utilities
Co-authored by @Alex2782 for the Android bindings. Many thanks to the reviewers also. Co-authored-by: Alex <alex.hart.278@gmail.com>
1 parent d09d82d commit 1b3e483

26 files changed

+331
-7
lines changed

core/core_bind.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,11 @@ String OS::get_cache_dir() const {
577577
return ::OS::get_singleton()->get_cache_path();
578578
}
579579

580+
String OS::get_temp_dir() const {
581+
// Exposed as `get_temp_dir()` instead of `get_temp_path()` for consistency with other exposed OS methods.
582+
return ::OS::get_singleton()->get_temp_path();
583+
}
584+
580585
bool OS::is_debug_build() const {
581586
#ifdef DEBUG_ENABLED
582587
return true;
@@ -705,6 +710,7 @@ void OS::_bind_methods() {
705710
ClassDB::bind_method(D_METHOD("get_config_dir"), &OS::get_config_dir);
706711
ClassDB::bind_method(D_METHOD("get_data_dir"), &OS::get_data_dir);
707712
ClassDB::bind_method(D_METHOD("get_cache_dir"), &OS::get_cache_dir);
713+
ClassDB::bind_method(D_METHOD("get_temp_dir"), &OS::get_temp_dir);
708714
ClassDB::bind_method(D_METHOD("get_unique_id"), &OS::get_unique_id);
709715

710716
ClassDB::bind_method(D_METHOD("get_keycode_string", "code"), &OS::get_keycode_string);

core/core_bind.h

+1
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ class OS : public Object {
262262
String get_config_dir() const;
263263
String get_data_dir() const;
264264
String get_cache_dir() const;
265+
String get_temp_dir() const;
265266

266267
Error set_thread_name(const String &p_name);
267268
::Thread::ID get_thread_caller_id() const;

core/io/dir_access.cpp

+80-1
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232

3333
#include "core/config/project_settings.h"
3434
#include "core/io/file_access.h"
35-
#include "core/os/memory.h"
3635
#include "core/os/os.h"
36+
#include "core/os/time.h"
3737
#include "core/templates/local_vector.h"
3838

3939
thread_local Error DirAccess::last_dir_open_error = OK;
@@ -323,6 +323,80 @@ Ref<DirAccess> DirAccess::create(AccessType p_access) {
323323
return da;
324324
}
325325

326+
Ref<DirAccess> DirAccess::create_temp(const String &p_prefix, bool p_keep, Error *r_error) {
327+
const String ERROR_COMMON_PREFIX = "Error while creating temporary directory";
328+
329+
if (!p_prefix.is_valid_filename()) {
330+
*r_error = ERR_FILE_BAD_PATH;
331+
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: "%s" is not a valid prefix.)", ERROR_COMMON_PREFIX, p_prefix));
332+
}
333+
334+
Ref<DirAccess> dir_access = DirAccess::open(OS::get_singleton()->get_temp_path());
335+
336+
uint32_t suffix_i = 0;
337+
String path;
338+
while (true) {
339+
String datetime = Time::get_singleton()->get_datetime_string_from_system().replace("-", "").replace("T", "").replace(":", "");
340+
datetime += itos(Time::get_singleton()->get_ticks_usec());
341+
String suffix = datetime + (suffix_i > 0 ? itos(suffix_i) : "");
342+
path = (p_prefix.is_empty() ? "" : p_prefix + "-") + suffix;
343+
if (!path.is_valid_filename()) {
344+
*r_error = ERR_FILE_BAD_PATH;
345+
return Ref<DirAccess>();
346+
}
347+
if (!DirAccess::exists(path)) {
348+
break;
349+
}
350+
suffix_i += 1;
351+
}
352+
353+
Error err = dir_access->make_dir(path);
354+
if (err != OK) {
355+
*r_error = err;
356+
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: "%s" couldn't create directory "%s".)", ERROR_COMMON_PREFIX, path));
357+
}
358+
err = dir_access->change_dir(path);
359+
if (err != OK) {
360+
*r_error = err;
361+
return Ref<DirAccess>();
362+
}
363+
364+
dir_access->_is_temp = true;
365+
dir_access->_temp_keep_after_free = p_keep;
366+
dir_access->_temp_path = dir_access->get_current_dir();
367+
368+
*r_error = OK;
369+
return dir_access;
370+
}
371+
372+
Ref<DirAccess> DirAccess::_create_temp(const String &p_prefix, bool p_keep) {
373+
return create_temp(p_prefix, p_keep, &last_dir_open_error);
374+
}
375+
376+
void DirAccess::_delete_temp() {
377+
if (!_is_temp || _temp_keep_after_free) {
378+
return;
379+
}
380+
381+
if (!DirAccess::exists(_temp_path)) {
382+
return;
383+
}
384+
385+
Error err;
386+
{
387+
Ref<DirAccess> dir_access = DirAccess::open(_temp_path, &err);
388+
if (err != OK) {
389+
return;
390+
}
391+
err = dir_access->erase_contents_recursive();
392+
if (err != OK) {
393+
return;
394+
}
395+
}
396+
397+
DirAccess::remove_absolute(_temp_path);
398+
}
399+
326400
Error DirAccess::get_open_error() {
327401
return last_dir_open_error;
328402
}
@@ -555,6 +629,7 @@ bool DirAccess::is_case_sensitive(const String &p_path) const {
555629
void DirAccess::_bind_methods() {
556630
ClassDB::bind_static_method("DirAccess", D_METHOD("open", "path"), &DirAccess::_open);
557631
ClassDB::bind_static_method("DirAccess", D_METHOD("get_open_error"), &DirAccess::get_open_error);
632+
ClassDB::bind_static_method("DirAccess", D_METHOD("create_temp", "prefix", "keep"), &DirAccess::_create_temp, DEFVAL(""), DEFVAL(false));
558633

559634
ClassDB::bind_method(D_METHOD("list_dir_begin"), &DirAccess::list_dir_begin, DEFVAL(false), DEFVAL(false));
560635
ClassDB::bind_method(D_METHOD("get_next"), &DirAccess::_get_next);
@@ -598,3 +673,7 @@ void DirAccess::_bind_methods() {
598673
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_navigational"), "set_include_navigational", "get_include_navigational");
599674
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_hidden"), "set_include_hidden", "get_include_hidden");
600675
}
676+
677+
DirAccess::~DirAccess() {
678+
_delete_temp();
679+
}

core/io/dir_access.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ class DirAccess : public RefCounted {
6161
bool include_navigational = false;
6262
bool include_hidden = false;
6363

64+
bool _is_temp = false;
65+
bool _temp_keep_after_free = false;
66+
String _temp_path;
67+
void _delete_temp();
68+
69+
static Ref<DirAccess> _create_temp(const String &p_prefix = "", bool p_keep = false);
70+
6471
protected:
6572
static void _bind_methods();
6673

@@ -136,6 +143,7 @@ class DirAccess : public RefCounted {
136143
}
137144

138145
static Ref<DirAccess> open(const String &p_path, Error *r_error = nullptr);
146+
static Ref<DirAccess> create_temp(const String &p_prefix = "", bool p_keep = false, Error *r_error = nullptr);
139147

140148
static int _get_drive_count();
141149
static String get_drive_name(int p_idx);
@@ -161,8 +169,9 @@ class DirAccess : public RefCounted {
161169

162170
virtual bool is_case_sensitive(const String &p_path) const;
163171

172+
public:
164173
DirAccess() {}
165-
virtual ~DirAccess() {}
174+
virtual ~DirAccess();
166175
};
167176

168177
#endif // DIR_ACCESS_H

core/io/file_access.cpp

+79
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "core/io/file_access_pack.h"
3939
#include "core/io/marshalls.h"
4040
#include "core/os/os.h"
41+
#include "core/os/time.h"
4142

4243
FileAccess::CreateFunc FileAccess::create_func[ACCESS_MAX] = {};
4344

@@ -84,6 +85,79 @@ Ref<FileAccess> FileAccess::create_for_path(const String &p_path) {
8485
return ret;
8586
}
8687

88+
Ref<FileAccess> FileAccess::create_temp(int p_mode_flags, const String &p_prefix, const String &p_extension, bool p_keep, Error *r_error) {
89+
const String ERROR_COMMON_PREFIX = "Error while creating temporary file";
90+
91+
if (!p_prefix.is_valid_filename()) {
92+
*r_error = ERR_FILE_BAD_PATH;
93+
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: "%s" is not a valid prefix.)", ERROR_COMMON_PREFIX, p_prefix));
94+
}
95+
96+
if (!p_extension.is_valid_filename()) {
97+
*r_error = ERR_FILE_BAD_PATH;
98+
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: "%s" is not a valid extension.)", ERROR_COMMON_PREFIX, p_extension));
99+
}
100+
101+
const String TEMP_DIR = OS::get_singleton()->get_temp_path();
102+
String extension = p_extension.trim_prefix(".");
103+
104+
uint32_t suffix_i = 0;
105+
String path;
106+
while (true) {
107+
String datetime = Time::get_singleton()->get_datetime_string_from_system().replace("-", "").replace("T", "").replace(":", "");
108+
datetime += itos(Time::get_singleton()->get_ticks_usec());
109+
String suffix = datetime + (suffix_i > 0 ? itos(suffix_i) : "");
110+
path = TEMP_DIR.path_join((p_prefix.is_empty() ? "" : p_prefix + "-") + suffix + (extension.is_empty() ? "" : "." + extension));
111+
if (!DirAccess::exists(path)) {
112+
break;
113+
}
114+
suffix_i += 1;
115+
}
116+
117+
Error err;
118+
{
119+
// Create file first with WRITE mode.
120+
// Otherwise, it would fail to open with a READ mode.
121+
Ref<FileAccess> ret = FileAccess::open(path, FileAccess::ModeFlags::WRITE, &err);
122+
if (err != OK) {
123+
*r_error = err;
124+
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: could not create "%s".)", ERROR_COMMON_PREFIX, path));
125+
}
126+
ret->flush();
127+
}
128+
129+
// Open then the temp file with the correct mode flag.
130+
Ref<FileAccess> ret = FileAccess::open(path, p_mode_flags, &err);
131+
if (err != OK) {
132+
*r_error = err;
133+
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: could not open "%s".)", ERROR_COMMON_PREFIX, path));
134+
}
135+
if (ret.is_valid()) {
136+
ret->_is_temp_file = true;
137+
ret->_temp_keep_after_use = p_keep;
138+
ret->_temp_path = ret->get_path_absolute();
139+
}
140+
141+
*r_error = OK;
142+
return ret;
143+
}
144+
145+
Ref<FileAccess> FileAccess::_create_temp(int p_mode_flags, const String &p_prefix, const String &p_extension, bool p_keep) {
146+
return create_temp(p_mode_flags, p_prefix, p_extension, p_keep, &last_file_open_error);
147+
}
148+
149+
void FileAccess::_delete_temp() {
150+
if (!_is_temp_file || _temp_keep_after_use) {
151+
return;
152+
}
153+
154+
if (!FileAccess::exists(_temp_path)) {
155+
return;
156+
}
157+
158+
DirAccess::remove_absolute(_temp_path);
159+
}
160+
87161
Error FileAccess::reopen(const String &p_path, int p_mode_flags) {
88162
return open_internal(p_path, p_mode_flags);
89163
}
@@ -823,6 +897,7 @@ void FileAccess::_bind_methods() {
823897
ClassDB::bind_static_method("FileAccess", D_METHOD("open_encrypted_with_pass", "path", "mode_flags", "pass"), &FileAccess::open_encrypted_pass);
824898
ClassDB::bind_static_method("FileAccess", D_METHOD("open_compressed", "path", "mode_flags", "compression_mode"), &FileAccess::open_compressed, DEFVAL(0));
825899
ClassDB::bind_static_method("FileAccess", D_METHOD("get_open_error"), &FileAccess::get_open_error);
900+
ClassDB::bind_static_method("FileAccess", D_METHOD("create_temp", "mode_flags", "prefix", "extension", "keep"), &FileAccess::_create_temp, DEFVAL(""), DEFVAL(""), DEFVAL(false));
826901

827902
ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_bytes", "path"), &FileAccess::_get_file_as_bytes);
828903
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() {
912987
BIND_BITFIELD_FLAG(UNIX_SET_GROUP_ID);
913988
BIND_BITFIELD_FLAG(UNIX_RESTRICTED_DELETE);
914989
}
990+
991+
FileAccess::~FileAccess() {
992+
_delete_temp();
993+
}

core/io/file_access.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ class FileAccess : public RefCounted {
128128

129129
static Ref<FileAccess> _open(const String &p_path, ModeFlags p_mode_flags);
130130

131+
bool _is_temp_file = false;
132+
bool _temp_keep_after_use = false;
133+
String _temp_path;
134+
void _delete_temp();
135+
136+
static Ref<FileAccess> _create_temp(int p_mode_flags, const String &p_prefix = "", const String &p_extension = "", bool p_keep = false);
137+
131138
public:
132139
static void set_file_close_fail_notify_callback(FileCloseFailNotify p_cbk) { close_fail_notify = p_cbk; }
133140

@@ -206,6 +213,7 @@ class FileAccess : public RefCounted {
206213
static Ref<FileAccess> create(AccessType p_access); /// Create a file access (for the current platform) this is the only portable way of accessing files.
207214
static Ref<FileAccess> create_for_path(const String &p_path);
208215
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.
216+
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);
209217

210218
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>());
211219
static Ref<FileAccess> open_encrypted_pass(const String &p_path, ModeFlags p_mode_flags, const String &p_pass);
@@ -241,8 +249,9 @@ class FileAccess : public RefCounted {
241249
create_func[p_access] = _create_builtin<T>;
242250
}
243251

252+
public:
244253
FileAccess() {}
245-
virtual ~FileAccess() {}
254+
virtual ~FileAccess();
246255
};
247256

248257
VARIANT_ENUM_CAST(FileAccess::CompressionMode);

core/os/os.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,10 @@ String OS::get_cache_path() const {
276276
return ".";
277277
}
278278

279+
String OS::get_temp_path() const {
280+
return ".";
281+
}
282+
279283
// Path to macOS .app bundle resources
280284
String OS::get_bundle_resource_dir() const {
281285
return ".";

core/os/os.h

+1
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ class OS {
287287
virtual String get_data_path() const;
288288
virtual String get_config_path() const;
289289
virtual String get_cache_path() const;
290+
virtual String get_temp_path() const;
290291
virtual String get_bundle_resource_dir() const;
291292
virtual String get_bundle_icon_path() const;
292293

doc/classes/DirAccess.xml

+11
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,17 @@
105105
[b]Note:[/b] This method is implemented on macOS, Linux, and Windows.
106106
</description>
107107
</method>
108+
<method name="create_temp" qualifiers="static">
109+
<return type="DirAccess" />
110+
<param index="0" name="prefix" type="String" default="&quot;&quot;" />
111+
<param index="1" name="keep" type="bool" default="false" />
112+
<description>
113+
Creates a temporary directory. This directory will be freed when the returned [DirAccess] is freed.
114+
If [param prefix] is not empty, it will be prefixed to the directory name, separated by a [code]-[/code].
115+
If [param keep] is [code]true[/code], the directory is not deleted when the returned [DirAccess] is freed.
116+
Returns [code]null[/code] if opening the directory failed. You can use [method get_open_error] to check the error that occurred.
117+
</description>
118+
</method>
108119
<method name="current_is_dir" qualifiers="const">
109120
<return type="bool" />
110121
<description>

doc/classes/FileAccess.xml

+14
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,20 @@
5050
[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.
5151
</description>
5252
</method>
53+
<method name="create_temp" qualifiers="static">
54+
<return type="FileAccess" />
55+
<param index="0" name="mode_flags" type="int" />
56+
<param index="1" name="prefix" type="String" default="&quot;&quot;" />
57+
<param index="2" name="extension" type="String" default="&quot;&quot;" />
58+
<param index="3" name="keep" type="bool" default="false" />
59+
<description>
60+
Creates a temporary file. This file will be freed when the returned [FileAccess] is freed.
61+
If [param prefix] is not empty, it will be prefixed to the file name, separated by a [code]-[/code].
62+
If [param extension] is not empty, it will be appended to the temporary file name.
63+
If [param keep] is [code]true[/code], the file is not deleted when the returned [FileAccess] is freed.
64+
Returns [code]null[/code] if opening the file failed. You can use [method get_open_error] to check the error that occurred.
65+
</description>
66+
</method>
5367
<method name="eof_reached" qualifiers="const">
5468
<return type="bool" />
5569
<description>

doc/classes/OS.xml

+6
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,12 @@
537537
[b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows.
538538
</description>
539539
</method>
540+
<method name="get_temp_dir" qualifiers="const">
541+
<return type="String" />
542+
<description>
543+
Returns the [i]global[/i] temporary data directory according to the operating system's standards.
544+
</description>
545+
</method>
540546
<method name="get_thread_caller_id" qualifiers="const">
541547
<return type="int" />
542548
<description>

drivers/unix/os_unix.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,10 @@ String OS_Unix::get_version() const {
308308
return "";
309309
}
310310

311+
String OS_Unix::get_temp_path() const {
312+
return "/tmp";
313+
}
314+
311315
double OS_Unix::get_unix_time() const {
312316
struct timeval tv_now;
313317
gettimeofday(&tv_now, nullptr);

drivers/unix/os_unix.h

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ class OS_Unix : public OS {
7676
virtual String get_distribution_name() const override;
7777
virtual String get_version() const override;
7878

79+
virtual String get_temp_path() const override;
80+
7981
virtual DateTime get_datetime(bool p_utc) const override;
8082
virtual TimeZoneInfo get_time_zone_info() const override;
8183

0 commit comments

Comments
 (0)