1 /*************************************************************************/
2 /* dir_access.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
10 /* */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the */
13 /* "Software"), to deal in the Software without restriction, including */
14 /* without limitation the rights to use, copy, modify, merge, publish, */
15 /* distribute, sublicense, and/or sell copies of the Software, and to */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions: */
18 /* */
19 /* The above copyright notice and this permission notice shall be */
20 /* included in all copies or substantial portions of the Software. */
21 /* */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29 /*************************************************************************/
30
31 #include "dir_access.h"
32
33 #include "core/os/file_access.h"
34 #include "core/os/memory.h"
35 #include "core/os/os.h"
36 #include "core/project_settings.h"
37
_get_root_path() const38 String DirAccess::_get_root_path() const {
39
40 switch (_access_type) {
41
42 case ACCESS_RESOURCES: return ProjectSettings::get_singleton()->get_resource_path();
43 case ACCESS_USERDATA: return OS::get_singleton()->get_user_data_dir();
44 default: return "";
45 }
46 }
_get_root_string() const47 String DirAccess::_get_root_string() const {
48
49 switch (_access_type) {
50
51 case ACCESS_RESOURCES: return "res://";
52 case ACCESS_USERDATA: return "user://";
53 default: return "";
54 }
55 }
56
get_current_drive()57 int DirAccess::get_current_drive() {
58
59 String path = get_current_dir().to_lower();
60 for (int i = 0; i < get_drive_count(); i++) {
61 String d = get_drive(i).to_lower();
62 if (path.begins_with(d))
63 return i;
64 }
65
66 return 0;
67 }
68
drives_are_shortcuts()69 bool DirAccess::drives_are_shortcuts() {
70
71 return false;
72 }
73
get_current_dir_without_drive()74 String DirAccess::get_current_dir_without_drive() {
75
76 return get_current_dir();
77 }
78
_erase_recursive(DirAccess * da)79 static Error _erase_recursive(DirAccess *da) {
80
81 List<String> dirs;
82 List<String> files;
83
84 da->list_dir_begin();
85 String n = da->get_next();
86 while (n != String()) {
87
88 if (n != "." && n != "..") {
89
90 if (da->current_is_dir())
91 dirs.push_back(n);
92 else
93 files.push_back(n);
94 }
95
96 n = da->get_next();
97 }
98
99 da->list_dir_end();
100
101 for (List<String>::Element *E = dirs.front(); E; E = E->next()) {
102
103 Error err = da->change_dir(E->get());
104 if (err == OK) {
105
106 err = _erase_recursive(da);
107 if (err) {
108 da->change_dir("..");
109 return err;
110 }
111 err = da->change_dir("..");
112 if (err) {
113 return err;
114 }
115 err = da->remove(da->get_current_dir().plus_file(E->get()));
116 if (err) {
117 return err;
118 }
119 } else {
120 return err;
121 }
122 }
123
124 for (List<String>::Element *E = files.front(); E; E = E->next()) {
125
126 Error err = da->remove(da->get_current_dir().plus_file(E->get()));
127 if (err) {
128 return err;
129 }
130 }
131
132 return OK;
133 }
134
erase_contents_recursive()135 Error DirAccess::erase_contents_recursive() {
136
137 return _erase_recursive(this);
138 }
139
make_dir_recursive(String p_dir)140 Error DirAccess::make_dir_recursive(String p_dir) {
141
142 if (p_dir.length() < 1) {
143 return OK;
144 };
145
146 String full_dir;
147
148 if (p_dir.is_rel_path()) {
149 //append current
150 full_dir = get_current_dir().plus_file(p_dir);
151
152 } else {
153 full_dir = p_dir;
154 }
155
156 full_dir = full_dir.replace("\\", "/");
157
158 //int slices = full_dir.get_slice_count("/");
159
160 String base;
161
162 if (full_dir.begins_with("res://"))
163 base = "res://";
164 else if (full_dir.begins_with("user://"))
165 base = "user://";
166 else if (full_dir.begins_with("/"))
167 base = "/";
168 else if (full_dir.find(":/") != -1) {
169 base = full_dir.substr(0, full_dir.find(":/") + 2);
170 } else {
171 ERR_FAIL_V(ERR_INVALID_PARAMETER);
172 }
173
174 full_dir = full_dir.replace_first(base, "").simplify_path();
175
176 Vector<String> subdirs = full_dir.split("/");
177
178 String curpath = base;
179 for (int i = 0; i < subdirs.size(); i++) {
180
181 curpath = curpath.plus_file(subdirs[i]);
182 Error err = make_dir(curpath);
183 if (err != OK && err != ERR_ALREADY_EXISTS) {
184
185 ERR_FAIL_V(err);
186 }
187 }
188
189 return OK;
190 }
191
fix_path(String p_path) const192 String DirAccess::fix_path(String p_path) const {
193
194 switch (_access_type) {
195
196 case ACCESS_RESOURCES: {
197
198 if (ProjectSettings::get_singleton()) {
199 if (p_path.begins_with("res://")) {
200
201 String resource_path = ProjectSettings::get_singleton()->get_resource_path();
202 if (resource_path != "") {
203
204 return p_path.replace_first("res:/", resource_path);
205 };
206 return p_path.replace_first("res://", "");
207 }
208 }
209
210 } break;
211 case ACCESS_USERDATA: {
212
213 if (p_path.begins_with("user://")) {
214
215 String data_dir = OS::get_singleton()->get_user_data_dir();
216 if (data_dir != "") {
217
218 return p_path.replace_first("user:/", data_dir);
219 };
220 return p_path.replace_first("user://", "");
221 }
222
223 } break;
224 case ACCESS_FILESYSTEM: {
225
226 return p_path;
227 } break;
228 case ACCESS_MAX: break; // Can't happen, but silences warning
229 }
230
231 return p_path;
232 }
233
234 DirAccess::CreateFunc DirAccess::create_func[ACCESS_MAX] = { 0, 0, 0 };
235
create_for_path(const String & p_path)236 DirAccess *DirAccess::create_for_path(const String &p_path) {
237
238 DirAccess *da = NULL;
239 if (p_path.begins_with("res://")) {
240
241 da = create(ACCESS_RESOURCES);
242 } else if (p_path.begins_with("user://")) {
243
244 da = create(ACCESS_USERDATA);
245 } else {
246
247 da = create(ACCESS_FILESYSTEM);
248 }
249
250 return da;
251 }
252
open(const String & p_path,Error * r_error)253 DirAccess *DirAccess::open(const String &p_path, Error *r_error) {
254
255 DirAccess *da = create_for_path(p_path);
256
257 ERR_FAIL_COND_V_MSG(!da, NULL, "Cannot create DirAccess for path '" + p_path + "'.");
258 Error err = da->change_dir(p_path);
259 if (r_error)
260 *r_error = err;
261 if (err != OK) {
262 memdelete(da);
263 return NULL;
264 }
265
266 return da;
267 }
268
create(AccessType p_access)269 DirAccess *DirAccess::create(AccessType p_access) {
270
271 DirAccess *da = create_func[p_access] ? create_func[p_access]() : NULL;
272 if (da) {
273 da->_access_type = p_access;
274 }
275
276 return da;
277 };
278
get_full_path(const String & p_path,AccessType p_access)279 String DirAccess::get_full_path(const String &p_path, AccessType p_access) {
280
281 DirAccess *d = DirAccess::create(p_access);
282 if (!d)
283 return p_path;
284
285 d->change_dir(p_path);
286 String full = d->get_current_dir();
287 memdelete(d);
288 return full;
289 }
290
copy(String p_from,String p_to,int p_chmod_flags)291 Error DirAccess::copy(String p_from, String p_to, int p_chmod_flags) {
292
293 //printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data());
294 Error err;
295 FileAccess *fsrc = FileAccess::open(p_from, FileAccess::READ, &err);
296
297 if (err) {
298 ERR_PRINTS("Failed to open " + p_from);
299 return err;
300 }
301
302 FileAccess *fdst = FileAccess::open(p_to, FileAccess::WRITE, &err);
303 if (err) {
304
305 fsrc->close();
306 memdelete(fsrc);
307 ERR_PRINTS("Failed to open " + p_to);
308 return err;
309 }
310
311 fsrc->seek_end(0);
312 int size = fsrc->get_position();
313 fsrc->seek(0);
314 err = OK;
315 while (size--) {
316
317 if (fsrc->get_error() != OK) {
318 err = fsrc->get_error();
319 break;
320 }
321 if (fdst->get_error() != OK) {
322 err = fdst->get_error();
323 break;
324 }
325
326 fdst->store_8(fsrc->get_8());
327 }
328
329 if (err == OK && p_chmod_flags != -1) {
330 fdst->close();
331 err = FileAccess::set_unix_permissions(p_to, p_chmod_flags);
332 // If running on a platform with no chmod support (i.e., Windows), don't fail
333 if (err == ERR_UNAVAILABLE)
334 err = OK;
335 }
336
337 memdelete(fsrc);
338 memdelete(fdst);
339
340 return err;
341 }
342
343 // Changes dir for the current scope, returning back to the original dir
344 // when scope exits
345 class DirChanger {
346 DirAccess *da;
347 String original_dir;
348
349 public:
DirChanger(DirAccess * p_da,String p_dir)350 DirChanger(DirAccess *p_da, String p_dir) :
351 da(p_da),
352 original_dir(p_da->get_current_dir()) {
353 p_da->change_dir(p_dir);
354 }
355
~DirChanger()356 ~DirChanger() {
357 da->change_dir(original_dir);
358 }
359 };
360
_copy_dir(DirAccess * p_target_da,String p_to,int p_chmod_flags)361 Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags) {
362 List<String> dirs;
363
364 String curdir = get_current_dir();
365 list_dir_begin();
366 String n = get_next();
367 while (n != String()) {
368
369 if (n != "." && n != "..") {
370
371 if (current_is_dir())
372 dirs.push_back(n);
373 else {
374 const String &rel_path = n;
375 if (!n.is_rel_path()) {
376 list_dir_end();
377 return ERR_BUG;
378 }
379 Error err = copy(get_current_dir().plus_file(n), p_to + rel_path, p_chmod_flags);
380 if (err) {
381 list_dir_end();
382 return err;
383 }
384 }
385 }
386
387 n = get_next();
388 }
389
390 list_dir_end();
391
392 for (List<String>::Element *E = dirs.front(); E; E = E->next()) {
393 String rel_path = E->get();
394 String target_dir = p_to + rel_path;
395 if (!p_target_da->dir_exists(target_dir)) {
396 Error err = p_target_da->make_dir(target_dir);
397 ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + target_dir + "'.");
398 }
399
400 Error err = change_dir(E->get());
401 ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + E->get() + "'.");
402
403 err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags);
404 if (err) {
405 change_dir("..");
406 ERR_FAIL_V_MSG(err, "Failed to copy recursively.");
407 }
408 err = change_dir("..");
409 ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to go back.");
410 }
411
412 return OK;
413 }
414
copy_dir(String p_from,String p_to,int p_chmod_flags)415 Error DirAccess::copy_dir(String p_from, String p_to, int p_chmod_flags) {
416 ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist.");
417
418 DirAccess *target_da = DirAccess::create_for_path(p_to);
419 ERR_FAIL_COND_V_MSG(!target_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_to + "'.");
420
421 if (!target_da->dir_exists(p_to)) {
422 Error err = target_da->make_dir_recursive(p_to);
423 if (err) {
424 memdelete(target_da);
425 }
426 ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + p_to + "'.");
427 }
428
429 if (!p_to.ends_with("/")) {
430 p_to = p_to + "/";
431 }
432
433 DirChanger dir_changer(this, p_from);
434 Error err = _copy_dir(target_da, p_to, p_chmod_flags);
435 memdelete(target_da);
436
437 return err;
438 }
439
exists(String p_dir)440 bool DirAccess::exists(String p_dir) {
441
442 DirAccess *da = DirAccess::create_for_path(p_dir);
443 bool valid = da->change_dir(p_dir) == OK;
444 memdelete(da);
445 return valid;
446 }
447
DirAccess()448 DirAccess::DirAccess() {
449
450 _access_type = ACCESS_FILESYSTEM;
451 }
452
~DirAccess()453 DirAccess::~DirAccess() {
454 }
455