1/**************************************************************************/
2/* editor_file_system.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
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 "editor_file_system.h"
32
33#include "core/config/project_settings.h"
34#include "core/extension/gdextension_manager.h"
35#include "core/io/file_access.h"
36#include "core/io/resource_importer.h"
37#include "core/io/resource_loader.h"
38#include "core/io/resource_saver.h"
39#include "core/object/worker_thread_pool.h"
40#include "core/os/os.h"
41#include "core/variant/variant_parser.h"
42#include "editor/editor_help.h"
43#include "editor/editor_node.h"
44#include "editor/editor_paths.h"
45#include "editor/editor_resource_preview.h"
46#include "editor/editor_settings.h"
47
48EditorFileSystem *EditorFileSystem::singleton = nullptr;
49//the name is the version, to keep compatibility with different versions of Godot
50#define CACHE_FILE_NAME "filesystem_cache8"
51
52void EditorFileSystemDirectory::sort_files() {
53 files.sort_custom<FileInfoSort>();
54}
55
56int EditorFileSystemDirectory::find_file_index(const String &p_file) const {
57 for (int i = 0; i < files.size(); i++) {
58 if (files[i]->file == p_file) {
59 return i;
60 }
61 }
62 return -1;
63}
64
65int EditorFileSystemDirectory::find_dir_index(const String &p_dir) const {
66 for (int i = 0; i < subdirs.size(); i++) {
67 if (subdirs[i]->name == p_dir) {
68 return i;
69 }
70 }
71
72 return -1;
73}
74
75void EditorFileSystemDirectory::force_update() {
76 // We set modified_time to 0 to force `EditorFileSystem::_scan_fs_changes` to search changes in the directory
77 modified_time = 0;
78}
79
80int EditorFileSystemDirectory::get_subdir_count() const {
81 return subdirs.size();
82}
83
84EditorFileSystemDirectory *EditorFileSystemDirectory::get_subdir(int p_idx) {
85 ERR_FAIL_INDEX_V(p_idx, subdirs.size(), nullptr);
86 return subdirs[p_idx];
87}
88
89int EditorFileSystemDirectory::get_file_count() const {
90 return files.size();
91}
92
93String EditorFileSystemDirectory::get_file(int p_idx) const {
94 ERR_FAIL_INDEX_V(p_idx, files.size(), "");
95
96 return files[p_idx]->file;
97}
98
99String EditorFileSystemDirectory::get_path() const {
100 String p;
101 const EditorFileSystemDirectory *d = this;
102 while (d->parent) {
103 p = d->name.path_join(p);
104 d = d->parent;
105 }
106
107 return "res://" + p;
108}
109
110String EditorFileSystemDirectory::get_file_path(int p_idx) const {
111 String file = get_file(p_idx);
112 const EditorFileSystemDirectory *d = this;
113 while (d->parent) {
114 file = d->name.path_join(file);
115 d = d->parent;
116 }
117
118 return "res://" + file;
119}
120
121Vector<String> EditorFileSystemDirectory::get_file_deps(int p_idx) const {
122 ERR_FAIL_INDEX_V(p_idx, files.size(), Vector<String>());
123 Vector<String> deps;
124
125 for (int i = 0; i < files[p_idx]->deps.size(); i++) {
126 String dep = files[p_idx]->deps[i];
127 int sep_idx = dep.find("::"); //may contain type information, unwanted
128 if (sep_idx != -1) {
129 dep = dep.substr(0, sep_idx);
130 }
131 ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(dep);
132 if (uid != ResourceUID::INVALID_ID) {
133 //return proper dependency resource from uid
134 if (ResourceUID::get_singleton()->has_id(uid)) {
135 dep = ResourceUID::get_singleton()->get_id_path(uid);
136 } else {
137 continue;
138 }
139 }
140 deps.push_back(dep);
141 }
142 return deps;
143}
144
145bool EditorFileSystemDirectory::get_file_import_is_valid(int p_idx) const {
146 ERR_FAIL_INDEX_V(p_idx, files.size(), false);
147 return files[p_idx]->import_valid;
148}
149
150uint64_t EditorFileSystemDirectory::get_file_modified_time(int p_idx) const {
151 ERR_FAIL_INDEX_V(p_idx, files.size(), 0);
152 return files[p_idx]->modified_time;
153}
154
155String EditorFileSystemDirectory::get_file_script_class_name(int p_idx) const {
156 return files[p_idx]->script_class_name;
157}
158
159String EditorFileSystemDirectory::get_file_script_class_extends(int p_idx) const {
160 return files[p_idx]->script_class_extends;
161}
162
163String EditorFileSystemDirectory::get_file_script_class_icon_path(int p_idx) const {
164 return files[p_idx]->script_class_icon_path;
165}
166
167StringName EditorFileSystemDirectory::get_file_type(int p_idx) const {
168 ERR_FAIL_INDEX_V(p_idx, files.size(), "");
169 return files[p_idx]->type;
170}
171
172StringName EditorFileSystemDirectory::get_file_resource_script_class(int p_idx) const {
173 ERR_FAIL_INDEX_V(p_idx, files.size(), "");
174 return files[p_idx]->resource_script_class;
175}
176
177String EditorFileSystemDirectory::get_name() {
178 return name;
179}
180
181EditorFileSystemDirectory *EditorFileSystemDirectory::get_parent() {
182 return parent;
183}
184
185void EditorFileSystemDirectory::_bind_methods() {
186 ClassDB::bind_method(D_METHOD("get_subdir_count"), &EditorFileSystemDirectory::get_subdir_count);
187 ClassDB::bind_method(D_METHOD("get_subdir", "idx"), &EditorFileSystemDirectory::get_subdir);
188 ClassDB::bind_method(D_METHOD("get_file_count"), &EditorFileSystemDirectory::get_file_count);
189 ClassDB::bind_method(D_METHOD("get_file", "idx"), &EditorFileSystemDirectory::get_file);
190 ClassDB::bind_method(D_METHOD("get_file_path", "idx"), &EditorFileSystemDirectory::get_file_path);
191 ClassDB::bind_method(D_METHOD("get_file_type", "idx"), &EditorFileSystemDirectory::get_file_type);
192 ClassDB::bind_method(D_METHOD("get_file_script_class_name", "idx"), &EditorFileSystemDirectory::get_file_script_class_name);
193 ClassDB::bind_method(D_METHOD("get_file_script_class_extends", "idx"), &EditorFileSystemDirectory::get_file_script_class_extends);
194 ClassDB::bind_method(D_METHOD("get_file_import_is_valid", "idx"), &EditorFileSystemDirectory::get_file_import_is_valid);
195 ClassDB::bind_method(D_METHOD("get_name"), &EditorFileSystemDirectory::get_name);
196 ClassDB::bind_method(D_METHOD("get_path"), &EditorFileSystemDirectory::get_path);
197 ClassDB::bind_method(D_METHOD("get_parent"), &EditorFileSystemDirectory::get_parent);
198 ClassDB::bind_method(D_METHOD("find_file_index", "name"), &EditorFileSystemDirectory::find_file_index);
199 ClassDB::bind_method(D_METHOD("find_dir_index", "name"), &EditorFileSystemDirectory::find_dir_index);
200}
201
202EditorFileSystemDirectory::EditorFileSystemDirectory() {
203 modified_time = 0;
204 parent = nullptr;
205}
206
207EditorFileSystemDirectory::~EditorFileSystemDirectory() {
208 for (int i = 0; i < files.size(); i++) {
209 memdelete(files[i]);
210 }
211
212 for (int i = 0; i < subdirs.size(); i++) {
213 memdelete(subdirs[i]);
214 }
215}
216
217void EditorFileSystem::_scan_filesystem() {
218 ERR_FAIL_COND(!scanning || new_filesystem);
219
220 //read .fscache
221 String cpath;
222
223 sources_changed.clear();
224 file_cache.clear();
225
226 String project = ProjectSettings::get_singleton()->get_resource_path();
227
228 String fscache = EditorPaths::get_singleton()->get_project_settings_dir().path_join(CACHE_FILE_NAME);
229 {
230 Ref<FileAccess> f = FileAccess::open(fscache, FileAccess::READ);
231
232 bool first = true;
233 if (f.is_valid()) {
234 //read the disk cache
235 while (!f->eof_reached()) {
236 String l = f->get_line().strip_edges();
237 if (first) {
238 if (first_scan) {
239 // only use this on first scan, afterwards it gets ignored
240 // this is so on first reimport we synchronize versions, then
241 // we don't care until editor restart. This is for usability mainly so
242 // your workflow is not killed after changing a setting by forceful reimporting
243 // everything there is.
244 filesystem_settings_version_for_import = l.strip_edges();
245 if (filesystem_settings_version_for_import != ResourceFormatImporter::get_singleton()->get_import_settings_hash()) {
246 revalidate_import_files = true;
247 }
248 }
249 first = false;
250 continue;
251 }
252 if (l.is_empty()) {
253 continue;
254 }
255
256 if (l.begins_with("::")) {
257 Vector<String> split = l.split("::");
258 ERR_CONTINUE(split.size() != 3);
259 String name = split[1];
260
261 cpath = name;
262
263 } else {
264 Vector<String> split = l.split("::");
265 ERR_CONTINUE(split.size() < 9);
266 String name = split[0];
267 String file;
268
269 file = name;
270 name = cpath.path_join(name);
271
272 FileCache fc;
273 fc.type = split[1];
274 if (fc.type.find("/") != -1) {
275 fc.type = fc.type.get_slice("/", 0);
276 fc.resource_script_class = fc.type.get_slice("/", 1);
277 }
278 fc.uid = split[2].to_int();
279 fc.modification_time = split[3].to_int();
280 fc.import_modification_time = split[4].to_int();
281 fc.import_valid = split[5].to_int() != 0;
282 fc.import_group_file = split[6].strip_edges();
283 fc.script_class_name = split[7].get_slice("<>", 0);
284 fc.script_class_extends = split[7].get_slice("<>", 1);
285 fc.script_class_icon_path = split[7].get_slice("<>", 2);
286
287 String deps = split[8].strip_edges();
288 if (deps.length()) {
289 Vector<String> dp = deps.split("<>");
290 for (int i = 0; i < dp.size(); i++) {
291 String path = dp[i];
292 fc.deps.push_back(path);
293 }
294 }
295
296 file_cache[name] = fc;
297 }
298 }
299 }
300 }
301
302 String update_cache = EditorPaths::get_singleton()->get_project_settings_dir().path_join("filesystem_update4");
303
304 if (FileAccess::exists(update_cache)) {
305 {
306 Ref<FileAccess> f2 = FileAccess::open(update_cache, FileAccess::READ);
307 String l = f2->get_line().strip_edges();
308 while (!l.is_empty()) {
309 file_cache.erase(l); //erase cache for this, so it gets updated
310 l = f2->get_line().strip_edges();
311 }
312 }
313
314 Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
315 d->remove(update_cache); //bye bye update cache
316 }
317
318 EditorProgressBG scan_progress("efs", "ScanFS", 1000);
319
320 ScanProgress sp;
321 sp.low = 0;
322 sp.hi = 1;
323 sp.progress = &scan_progress;
324
325 new_filesystem = memnew(EditorFileSystemDirectory);
326 new_filesystem->parent = nullptr;
327
328 Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
329 d->change_dir("res://");
330 _scan_new_dir(new_filesystem, d, sp);
331
332 file_cache.clear(); //clear caches, no longer needed
333
334 if (!first_scan) {
335 //on the first scan this is done from the main thread after re-importing
336 _save_filesystem_cache();
337 }
338
339 scanning = false;
340}
341
342void EditorFileSystem::_save_filesystem_cache() {
343 group_file_cache.clear();
344
345 String fscache = EditorPaths::get_singleton()->get_project_settings_dir().path_join(CACHE_FILE_NAME);
346
347 Ref<FileAccess> f = FileAccess::open(fscache, FileAccess::WRITE);
348 ERR_FAIL_COND_MSG(f.is_null(), "Cannot create file '" + fscache + "'. Check user write permissions.");
349
350 f->store_line(filesystem_settings_version_for_import);
351 _save_filesystem_cache(filesystem, f);
352}
353
354void EditorFileSystem::_thread_func(void *_userdata) {
355 EditorFileSystem *sd = (EditorFileSystem *)_userdata;
356 sd->_scan_filesystem();
357}
358
359bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_imported_files) {
360 if (!reimport_on_missing_imported_files && p_only_imported_files) {
361 return false;
362 }
363
364 if (!FileAccess::exists(p_path + ".import")) {
365 return true;
366 }
367
368 if (!ResourceFormatImporter::get_singleton()->are_import_settings_valid(p_path)) {
369 //reimport settings are not valid, reimport
370 return true;
371 }
372
373 Error err;
374 Ref<FileAccess> f = FileAccess::open(p_path + ".import", FileAccess::READ, &err);
375
376 if (f.is_null()) { //no import file, do reimport
377 return true;
378 }
379
380 VariantParser::StreamFile stream;
381 stream.f = f;
382
383 String assign;
384 Variant value;
385 VariantParser::Tag next_tag;
386
387 int lines = 0;
388 String error_text;
389
390 List<String> to_check;
391
392 String importer_name;
393 String source_file = "";
394 String source_md5 = "";
395 Vector<String> dest_files;
396 String dest_md5 = "";
397 int version = 0;
398 bool found_uid = false;
399
400 while (true) {
401 assign = Variant();
402 next_tag.fields.clear();
403 next_tag.name = String();
404
405 err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true);
406 if (err == ERR_FILE_EOF) {
407 break;
408 } else if (err != OK) {
409 ERR_PRINT("ResourceFormatImporter::load - '" + p_path + ".import:" + itos(lines) + "' error '" + error_text + "'.");
410 return false; //parse error, try reimport manually (Avoid reimport loop on broken file)
411 }
412
413 if (!assign.is_empty()) {
414 if (assign.begins_with("path")) {
415 to_check.push_back(value);
416 } else if (assign == "files") {
417 Array fa = value;
418 for (int i = 0; i < fa.size(); i++) {
419 to_check.push_back(fa[i]);
420 }
421 } else if (assign == "importer_version") {
422 version = value;
423 } else if (assign == "importer") {
424 importer_name = value;
425 } else if (assign == "uid") {
426 found_uid = true;
427 } else if (!p_only_imported_files) {
428 if (assign == "source_file") {
429 source_file = value;
430 } else if (assign == "dest_files") {
431 dest_files = value;
432 }
433 }
434
435 } else if (next_tag.name != "remap" && next_tag.name != "deps") {
436 break;
437 }
438 }
439
440 if (importer_name == "keep") {
441 return false; //keep mode, do not reimport
442 }
443
444 if (!found_uid) {
445 return true; //UID not found, old format, reimport.
446 }
447
448 Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name);
449
450 if (importer.is_null()) {
451 return true; // the importer has possibly changed, try to reimport.
452 }
453
454 if (importer->get_format_version() > version) {
455 return true; // version changed, reimport
456 }
457
458 // Read the md5's from a separate file (so the import parameters aren't dependent on the file version
459 String base_path = ResourceFormatImporter::get_singleton()->get_import_base_path(p_path);
460 Ref<FileAccess> md5s = FileAccess::open(base_path + ".md5", FileAccess::READ, &err);
461 if (md5s.is_null()) { // No md5's stored for this resource
462 return true;
463 }
464
465 VariantParser::StreamFile md5_stream;
466 md5_stream.f = md5s;
467
468 while (true) {
469 assign = Variant();
470 next_tag.fields.clear();
471 next_tag.name = String();
472
473 err = VariantParser::parse_tag_assign_eof(&md5_stream, lines, error_text, next_tag, assign, value, nullptr, true);
474
475 if (err == ERR_FILE_EOF) {
476 break;
477 } else if (err != OK) {
478 ERR_PRINT("ResourceFormatImporter::load - '" + p_path + ".import.md5:" + itos(lines) + "' error '" + error_text + "'.");
479 return false; // parse error
480 }
481 if (!assign.is_empty()) {
482 if (!p_only_imported_files) {
483 if (assign == "source_md5") {
484 source_md5 = value;
485 } else if (assign == "dest_md5") {
486 dest_md5 = value;
487 }
488 }
489 }
490 }
491
492 //imported files are gone, reimport
493 for (const String &E : to_check) {
494 if (!FileAccess::exists(E)) {
495 return true;
496 }
497 }
498
499 //check source md5 matching
500 if (!p_only_imported_files) {
501 if (!source_file.is_empty() && source_file != p_path) {
502 return true; //file was moved, reimport
503 }
504
505 if (source_md5.is_empty()) {
506 return true; //lacks md5, so just reimport
507 }
508
509 String md5 = FileAccess::get_md5(p_path);
510 if (md5 != source_md5) {
511 return true;
512 }
513
514 if (dest_files.size() && !dest_md5.is_empty()) {
515 md5 = FileAccess::get_multiple_md5(dest_files);
516 if (md5 != dest_md5) {
517 return true;
518 }
519 }
520 }
521
522 return false; //nothing changed
523}
524
525bool EditorFileSystem::_scan_import_support(Vector<String> reimports) {
526 if (import_support_queries.size() == 0) {
527 return false;
528 }
529 HashMap<String, int> import_support_test;
530 Vector<bool> import_support_tested;
531 import_support_tested.resize(import_support_queries.size());
532 for (int i = 0; i < import_support_queries.size(); i++) {
533 import_support_tested.write[i] = false;
534 if (import_support_queries[i]->is_active()) {
535 Vector<String> extensions = import_support_queries[i]->get_file_extensions();
536 for (int j = 0; j < extensions.size(); j++) {
537 import_support_test.insert(extensions[j], i);
538 }
539 }
540 }
541
542 if (import_support_test.size() == 0) {
543 return false; //well nothing to do
544 }
545
546 for (int i = 0; i < reimports.size(); i++) {
547 HashMap<String, int>::Iterator E = import_support_test.find(reimports[i].get_extension().to_lower());
548 if (E) {
549 import_support_tested.write[E->value] = true;
550 }
551 }
552
553 for (int i = 0; i < import_support_tested.size(); i++) {
554 if (import_support_tested[i]) {
555 if (import_support_queries.write[i]->query()) {
556 return true;
557 }
558 }
559 }
560
561 return false;
562}
563
564bool EditorFileSystem::_update_scan_actions() {
565 sources_changed.clear();
566
567 bool fs_changed = false;
568
569 Vector<String> reimports;
570 Vector<String> reloads;
571
572 for (const ItemAction &ia : scan_actions) {
573 switch (ia.action) {
574 case ItemAction::ACTION_NONE: {
575 } break;
576 case ItemAction::ACTION_DIR_ADD: {
577 int idx = 0;
578 for (int i = 0; i < ia.dir->subdirs.size(); i++) {
579 if (ia.new_dir->name.naturalnocasecmp_to(ia.dir->subdirs[i]->name) < 0) {
580 break;
581 }
582 idx++;
583 }
584 if (idx == ia.dir->subdirs.size()) {
585 ia.dir->subdirs.push_back(ia.new_dir);
586 } else {
587 ia.dir->subdirs.insert(idx, ia.new_dir);
588 }
589
590 fs_changed = true;
591 } break;
592 case ItemAction::ACTION_DIR_REMOVE: {
593 ERR_CONTINUE(!ia.dir->parent);
594 ia.dir->parent->subdirs.erase(ia.dir);
595 memdelete(ia.dir);
596 fs_changed = true;
597 } break;
598 case ItemAction::ACTION_FILE_ADD: {
599 int idx = 0;
600 for (int i = 0; i < ia.dir->files.size(); i++) {
601 if (ia.new_file->file.naturalnocasecmp_to(ia.dir->files[i]->file) < 0) {
602 break;
603 }
604 idx++;
605 }
606 if (idx == ia.dir->files.size()) {
607 ia.dir->files.push_back(ia.new_file);
608 } else {
609 ia.dir->files.insert(idx, ia.new_file);
610 }
611
612 fs_changed = true;
613
614 if (ClassDB::is_parent_class(ia.new_file->type, SNAME("Script"))) {
615 _queue_update_script_class(ia.dir->get_file_path(idx));
616 }
617
618 } break;
619 case ItemAction::ACTION_FILE_REMOVE: {
620 int idx = ia.dir->find_file_index(ia.file);
621 ERR_CONTINUE(idx == -1);
622
623 if (ClassDB::is_parent_class(ia.dir->files[idx]->type, SNAME("Script"))) {
624 _queue_update_script_class(ia.dir->get_file_path(idx));
625 }
626
627 _delete_internal_files(ia.dir->files[idx]->file);
628 memdelete(ia.dir->files[idx]);
629 ia.dir->files.remove_at(idx);
630
631 fs_changed = true;
632
633 } break;
634 case ItemAction::ACTION_FILE_TEST_REIMPORT: {
635 int idx = ia.dir->find_file_index(ia.file);
636 ERR_CONTINUE(idx == -1);
637 String full_path = ia.dir->get_file_path(idx);
638 if (_test_for_reimport(full_path, false)) {
639 //must reimport
640 reimports.push_back(full_path);
641 Vector<String> dependencies = _get_dependencies(full_path);
642 for (const String &dependency_path : dependencies) {
643 if (import_extensions.has(dependency_path.get_extension())) {
644 reimports.push_back(dependency_path);
645 }
646 }
647 } else {
648 //must not reimport, all was good
649 //update modified times, to avoid reimport
650 ia.dir->files[idx]->modified_time = FileAccess::get_modified_time(full_path);
651 ia.dir->files[idx]->import_modified_time = FileAccess::get_modified_time(full_path + ".import");
652 }
653
654 fs_changed = true;
655 } break;
656 case ItemAction::ACTION_FILE_RELOAD: {
657 int idx = ia.dir->find_file_index(ia.file);
658 ERR_CONTINUE(idx == -1);
659 String full_path = ia.dir->get_file_path(idx);
660
661 if (ClassDB::is_parent_class(ia.dir->files[idx]->type, SNAME("Script"))) {
662 _queue_update_script_class(full_path);
663 }
664
665 reloads.push_back(full_path);
666
667 } break;
668 }
669 }
670
671 if (_scan_extensions()) {
672 //needs editor restart
673 //extensions also may provide filetypes to be imported, so they must run before importing
674 if (EditorNode::immediate_confirmation_dialog(TTR("Some extensions need the editor to restart to take effect."), first_scan ? TTR("Restart") : TTR("Save & Restart"), TTR("Continue"))) {
675 if (!first_scan) {
676 EditorNode::get_singleton()->save_all_scenes();
677 }
678 EditorNode::get_singleton()->restart_editor();
679 //do not import
680 return true;
681 }
682 }
683
684 if (reimports.size()) {
685 if (_scan_import_support(reimports)) {
686 return true;
687 }
688
689 reimport_files(reimports);
690 } else {
691 //reimport files will update the uid cache file so if nothing was reimported, update it manually
692 ResourceUID::get_singleton()->update_cache();
693 }
694
695 if (first_scan) {
696 //only on first scan this is valid and updated, then settings changed.
697 revalidate_import_files = false;
698 filesystem_settings_version_for_import = ResourceFormatImporter::get_singleton()->get_import_settings_hash();
699 _save_filesystem_cache();
700 }
701
702 if (reloads.size()) {
703 emit_signal(SNAME("resources_reload"), reloads);
704 }
705 scan_actions.clear();
706
707 return fs_changed;
708}
709
710void EditorFileSystem::scan() {
711 if (false /*&& bool(Globals::get_singleton()->get("debug/disable_scan"))*/) {
712 return;
713 }
714
715 if (scanning || scanning_changes || thread.is_started()) {
716 return;
717 }
718
719 _update_extensions();
720
721 if (!use_threads) {
722 scanning = true;
723 scan_total = 0;
724 _scan_filesystem();
725 if (filesystem) {
726 memdelete(filesystem);
727 }
728 //file_type_cache.clear();
729 filesystem = new_filesystem;
730 new_filesystem = nullptr;
731 _update_scan_actions();
732 scanning = false;
733 _update_pending_script_classes();
734 emit_signal(SNAME("filesystem_changed"));
735 emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
736 first_scan = false;
737 } else {
738 ERR_FAIL_COND(thread.is_started());
739 set_process(true);
740 Thread::Settings s;
741 scanning = true;
742 scan_total = 0;
743 s.priority = Thread::PRIORITY_LOW;
744 thread.start(_thread_func, this, s);
745 //tree->hide();
746 //progress->show();
747 }
748}
749
750void EditorFileSystem::ScanProgress::update(int p_current, int p_total) const {
751 float ratio = low + ((hi - low) / p_total) * p_current;
752 progress->step(ratio * 1000);
753 EditorFileSystem::singleton->scan_total = ratio;
754}
755
756EditorFileSystem::ScanProgress EditorFileSystem::ScanProgress::get_sub(int p_current, int p_total) const {
757 ScanProgress sp = *this;
758 float slice = (sp.hi - sp.low) / p_total;
759 sp.low += slice * p_current;
760 sp.hi = slice;
761 return sp;
762}
763
764void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAccess> &da, const ScanProgress &p_progress) {
765 List<String> dirs;
766 List<String> files;
767
768 String cd = da->get_current_dir();
769
770 p_dir->modified_time = FileAccess::get_modified_time(cd);
771
772 da->list_dir_begin();
773 while (true) {
774 String f = da->get_next();
775 if (f.is_empty()) {
776 break;
777 }
778
779 if (da->current_is_hidden()) {
780 continue;
781 }
782
783 if (da->current_is_dir()) {
784 if (f.begins_with(".")) { // Ignore special and . / ..
785 continue;
786 }
787
788 if (_should_skip_directory(cd.path_join(f))) {
789 continue;
790 }
791
792 dirs.push_back(f);
793
794 } else {
795 files.push_back(f);
796 }
797 }
798
799 da->list_dir_end();
800
801 dirs.sort_custom<NaturalNoCaseComparator>();
802 files.sort_custom<NaturalNoCaseComparator>();
803
804 int total = dirs.size() + files.size();
805 int idx = 0;
806
807 for (List<String>::Element *E = dirs.front(); E; E = E->next(), idx++) {
808 if (da->change_dir(E->get()) == OK) {
809 String d = da->get_current_dir();
810
811 if (d == cd || !d.begins_with(cd)) {
812 da->change_dir(cd); //avoid recursion
813 } else {
814 EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory);
815
816 efd->parent = p_dir;
817 efd->name = E->get();
818
819 _scan_new_dir(efd, da, p_progress.get_sub(idx, total));
820
821 int idx2 = 0;
822 for (int i = 0; i < p_dir->subdirs.size(); i++) {
823 if (efd->name.naturalnocasecmp_to(p_dir->subdirs[i]->name) < 0) {
824 break;
825 }
826 idx2++;
827 }
828 if (idx2 == p_dir->subdirs.size()) {
829 p_dir->subdirs.push_back(efd);
830 } else {
831 p_dir->subdirs.insert(idx2, efd);
832 }
833
834 da->change_dir("..");
835 }
836 } else {
837 ERR_PRINT("Cannot go into subdir '" + E->get() + "'.");
838 }
839
840 p_progress.update(idx, total);
841 }
842
843 for (List<String>::Element *E = files.front(); E; E = E->next(), idx++) {
844 String ext = E->get().get_extension().to_lower();
845 if (!valid_extensions.has(ext)) {
846 continue; //invalid
847 }
848
849 EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo);
850 fi->file = E->get();
851
852 String path = cd.path_join(fi->file);
853
854 FileCache *fc = file_cache.getptr(path);
855 uint64_t mt = FileAccess::get_modified_time(path);
856
857 if (import_extensions.has(ext)) {
858 //is imported
859 uint64_t import_mt = 0;
860 if (FileAccess::exists(path + ".import")) {
861 import_mt = FileAccess::get_modified_time(path + ".import");
862 }
863
864 if (fc && fc->modification_time == mt && fc->import_modification_time == import_mt && !_test_for_reimport(path, true)) {
865 fi->type = fc->type;
866 fi->resource_script_class = fc->resource_script_class;
867 fi->uid = fc->uid;
868 fi->deps = fc->deps;
869 fi->modified_time = fc->modification_time;
870 fi->import_modified_time = fc->import_modification_time;
871
872 fi->import_valid = fc->import_valid;
873 fi->script_class_name = fc->script_class_name;
874 fi->import_group_file = fc->import_group_file;
875 fi->script_class_extends = fc->script_class_extends;
876 fi->script_class_icon_path = fc->script_class_icon_path;
877
878 if (revalidate_import_files && !ResourceFormatImporter::get_singleton()->are_import_settings_valid(path)) {
879 ItemAction ia;
880 ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
881 ia.dir = p_dir;
882 ia.file = E->get();
883 scan_actions.push_back(ia);
884 }
885
886 if (fc->type.is_empty()) {
887 fi->type = ResourceLoader::get_resource_type(path);
888 fi->resource_script_class = ResourceLoader::get_resource_script_class(path);
889 fi->import_group_file = ResourceLoader::get_import_group_file(path);
890 //there is also the chance that file type changed due to reimport, must probably check this somehow here (or kind of note it for next time in another file?)
891 //note: I think this should not happen any longer..
892 }
893
894 if (fc->uid == ResourceUID::INVALID_ID) {
895 // imported files should always have a UID, so attempt to fetch it.
896 fi->uid = ResourceLoader::get_resource_uid(path);
897 }
898
899 } else {
900 fi->type = ResourceFormatImporter::get_singleton()->get_resource_type(path);
901 fi->uid = ResourceFormatImporter::get_singleton()->get_resource_uid(path);
902 fi->import_group_file = ResourceFormatImporter::get_singleton()->get_import_group_file(path);
903 fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
904 fi->modified_time = 0;
905 fi->import_modified_time = 0;
906 fi->import_valid = fi->type == "TextFile" ? true : ResourceLoader::is_import_valid(path);
907
908 ItemAction ia;
909 ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
910 ia.dir = p_dir;
911 ia.file = E->get();
912 scan_actions.push_back(ia);
913 }
914 } else {
915 if (fc && fc->modification_time == mt) {
916 //not imported, so just update type if changed
917 fi->type = fc->type;
918 fi->resource_script_class = fc->resource_script_class;
919 fi->uid = fc->uid;
920 fi->modified_time = fc->modification_time;
921 fi->deps = fc->deps;
922 fi->import_modified_time = 0;
923 fi->import_valid = true;
924 fi->script_class_name = fc->script_class_name;
925 fi->script_class_extends = fc->script_class_extends;
926 fi->script_class_icon_path = fc->script_class_icon_path;
927 } else {
928 //new or modified time
929 fi->type = ResourceLoader::get_resource_type(path);
930 fi->resource_script_class = ResourceLoader::get_resource_script_class(path);
931 if (fi->type == "" && textfile_extensions.has(ext)) {
932 fi->type = "TextFile";
933 }
934 fi->uid = ResourceLoader::get_resource_uid(path);
935 fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
936 fi->deps = _get_dependencies(path);
937 fi->modified_time = mt;
938 fi->import_modified_time = 0;
939 fi->import_valid = true;
940
941 if (ClassDB::is_parent_class(fi->type, SNAME("Script"))) {
942 _queue_update_script_class(path);
943 }
944 }
945 }
946
947 if (fi->uid != ResourceUID::INVALID_ID) {
948 if (ResourceUID::get_singleton()->has_id(fi->uid)) {
949 ResourceUID::get_singleton()->set_id(fi->uid, path);
950 } else {
951 ResourceUID::get_singleton()->add_id(fi->uid, path);
952 }
953 }
954
955 p_dir->files.push_back(fi);
956 p_progress.update(idx, total);
957 }
958}
959
960void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const ScanProgress &p_progress) {
961 uint64_t current_mtime = FileAccess::get_modified_time(p_dir->get_path());
962
963 bool updated_dir = false;
964 String cd = p_dir->get_path();
965
966 if (current_mtime != p_dir->modified_time || using_fat32_or_exfat) {
967 updated_dir = true;
968 p_dir->modified_time = current_mtime;
969 //ooooops, dir changed, see what's going on
970
971 //first mark everything as veryfied
972
973 for (int i = 0; i < p_dir->files.size(); i++) {
974 p_dir->files[i]->verified = false;
975 }
976
977 for (int i = 0; i < p_dir->subdirs.size(); i++) {
978 p_dir->get_subdir(i)->verified = false;
979 }
980
981 //then scan files and directories and check what's different
982
983 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
984
985 Error ret = da->change_dir(cd);
986 ERR_FAIL_COND_MSG(ret != OK, "Cannot change to '" + cd + "' folder.");
987
988 da->list_dir_begin();
989 while (true) {
990 String f = da->get_next();
991 if (f.is_empty()) {
992 break;
993 }
994
995 if (da->current_is_hidden()) {
996 continue;
997 }
998
999 if (da->current_is_dir()) {
1000 if (f.begins_with(".")) { // Ignore special and . / ..
1001 continue;
1002 }
1003
1004 int idx = p_dir->find_dir_index(f);
1005 if (idx == -1) {
1006 if (_should_skip_directory(cd.path_join(f))) {
1007 continue;
1008 }
1009
1010 EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory);
1011
1012 efd->parent = p_dir;
1013 efd->name = f;
1014 Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
1015 d->change_dir(cd.path_join(f));
1016 _scan_new_dir(efd, d, p_progress.get_sub(1, 1));
1017
1018 ItemAction ia;
1019 ia.action = ItemAction::ACTION_DIR_ADD;
1020 ia.dir = p_dir;
1021 ia.file = f;
1022 ia.new_dir = efd;
1023 scan_actions.push_back(ia);
1024 } else {
1025 p_dir->subdirs[idx]->verified = true;
1026 }
1027
1028 } else {
1029 String ext = f.get_extension().to_lower();
1030 if (!valid_extensions.has(ext)) {
1031 continue; //invalid
1032 }
1033
1034 int idx = p_dir->find_file_index(f);
1035
1036 if (idx == -1) {
1037 //never seen this file, add actition to add it
1038 EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo);
1039 fi->file = f;
1040
1041 String path = cd.path_join(fi->file);
1042 fi->modified_time = FileAccess::get_modified_time(path);
1043 fi->import_modified_time = 0;
1044 fi->type = ResourceLoader::get_resource_type(path);
1045 fi->resource_script_class = ResourceLoader::get_resource_script_class(path);
1046 if (fi->type == "" && textfile_extensions.has(ext)) {
1047 fi->type = "TextFile";
1048 }
1049 fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
1050 fi->import_valid = fi->type == "TextFile" ? true : ResourceLoader::is_import_valid(path);
1051 fi->import_group_file = ResourceLoader::get_import_group_file(path);
1052
1053 {
1054 ItemAction ia;
1055 ia.action = ItemAction::ACTION_FILE_ADD;
1056 ia.dir = p_dir;
1057 ia.file = f;
1058 ia.new_file = fi;
1059 scan_actions.push_back(ia);
1060 }
1061
1062 if (import_extensions.has(ext)) {
1063 //if it can be imported, and it was added, it needs to be reimported
1064 ItemAction ia;
1065 ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
1066 ia.dir = p_dir;
1067 ia.file = f;
1068 scan_actions.push_back(ia);
1069 }
1070
1071 } else {
1072 p_dir->files[idx]->verified = true;
1073 }
1074 }
1075 }
1076
1077 da->list_dir_end();
1078 }
1079
1080 for (int i = 0; i < p_dir->files.size(); i++) {
1081 if (updated_dir && !p_dir->files[i]->verified) {
1082 //this file was removed, add action to remove it
1083 ItemAction ia;
1084 ia.action = ItemAction::ACTION_FILE_REMOVE;
1085 ia.dir = p_dir;
1086 ia.file = p_dir->files[i]->file;
1087 scan_actions.push_back(ia);
1088 continue;
1089 }
1090
1091 String path = cd.path_join(p_dir->files[i]->file);
1092
1093 if (import_extensions.has(p_dir->files[i]->file.get_extension().to_lower())) {
1094 //check here if file must be imported or not
1095
1096 uint64_t mt = FileAccess::get_modified_time(path);
1097
1098 bool reimport = false;
1099
1100 if (mt != p_dir->files[i]->modified_time) {
1101 reimport = true; //it was modified, must be reimported.
1102 } else if (!FileAccess::exists(path + ".import")) {
1103 reimport = true; //no .import file, obviously reimport
1104 } else {
1105 uint64_t import_mt = FileAccess::get_modified_time(path + ".import");
1106 if (import_mt != p_dir->files[i]->import_modified_time) {
1107 reimport = true;
1108 } else if (_test_for_reimport(path, true)) {
1109 reimport = true;
1110 }
1111 }
1112
1113 if (reimport) {
1114 ItemAction ia;
1115 ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
1116 ia.dir = p_dir;
1117 ia.file = p_dir->files[i]->file;
1118 scan_actions.push_back(ia);
1119 }
1120 } else if (ResourceCache::has(path)) { //test for potential reload
1121
1122 uint64_t mt = FileAccess::get_modified_time(path);
1123
1124 if (mt != p_dir->files[i]->modified_time) {
1125 p_dir->files[i]->modified_time = mt; //save new time, but test for reload
1126
1127 ItemAction ia;
1128 ia.action = ItemAction::ACTION_FILE_RELOAD;
1129 ia.dir = p_dir;
1130 ia.file = p_dir->files[i]->file;
1131 scan_actions.push_back(ia);
1132 }
1133 }
1134 }
1135
1136 for (int i = 0; i < p_dir->subdirs.size(); i++) {
1137 if ((updated_dir && !p_dir->subdirs[i]->verified) || _should_skip_directory(p_dir->subdirs[i]->get_path())) {
1138 //this directory was removed or ignored, add action to remove it
1139 ItemAction ia;
1140 ia.action = ItemAction::ACTION_DIR_REMOVE;
1141 ia.dir = p_dir->subdirs[i];
1142 scan_actions.push_back(ia);
1143 continue;
1144 }
1145 _scan_fs_changes(p_dir->get_subdir(i), p_progress);
1146 }
1147}
1148
1149void EditorFileSystem::_delete_internal_files(String p_file) {
1150 if (FileAccess::exists(p_file + ".import")) {
1151 List<String> paths;
1152 ResourceFormatImporter::get_singleton()->get_internal_resource_path_list(p_file, &paths);
1153 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
1154 for (const String &E : paths) {
1155 da->remove(E);
1156 }
1157 da->remove(p_file + ".import");
1158 }
1159}
1160
1161void EditorFileSystem::_thread_func_sources(void *_userdata) {
1162 EditorFileSystem *efs = (EditorFileSystem *)_userdata;
1163 if (efs->filesystem) {
1164 EditorProgressBG pr("sources", TTR("ScanSources"), 1000);
1165 ScanProgress sp;
1166 sp.progress = &pr;
1167 sp.hi = 1;
1168 sp.low = 0;
1169 efs->_scan_fs_changes(efs->filesystem, sp);
1170 }
1171 efs->scanning_changes_done = true;
1172}
1173
1174void EditorFileSystem::scan_changes() {
1175 if (first_scan || // Prevent a premature changes scan from inhibiting the first full scan
1176 scanning || scanning_changes || thread.is_started()) {
1177 scan_changes_pending = true;
1178 set_process(true);
1179 return;
1180 }
1181
1182 _update_extensions();
1183 sources_changed.clear();
1184 scanning_changes = true;
1185 scanning_changes_done = false;
1186
1187 if (!use_threads) {
1188 if (filesystem) {
1189 EditorProgressBG pr("sources", TTR("ScanSources"), 1000);
1190 ScanProgress sp;
1191 sp.progress = &pr;
1192 sp.hi = 1;
1193 sp.low = 0;
1194 scan_total = 0;
1195 _scan_fs_changes(filesystem, sp);
1196 bool changed = _update_scan_actions();
1197 _update_pending_script_classes();
1198 if (changed) {
1199 emit_signal(SNAME("filesystem_changed"));
1200 }
1201 }
1202 scanning_changes = false;
1203 scanning_changes_done = true;
1204 emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
1205 } else {
1206 ERR_FAIL_COND(thread_sources.is_started());
1207 set_process(true);
1208 scan_total = 0;
1209 Thread::Settings s;
1210 s.priority = Thread::PRIORITY_LOW;
1211 thread_sources.start(_thread_func_sources, this, s);
1212 }
1213}
1214
1215void EditorFileSystem::_notification(int p_what) {
1216 switch (p_what) {
1217 case NOTIFICATION_EXIT_TREE: {
1218 Thread &active_thread = thread.is_started() ? thread : thread_sources;
1219 if (use_threads && active_thread.is_started()) {
1220 while (scanning) {
1221 OS::get_singleton()->delay_usec(1000);
1222 }
1223 active_thread.wait_to_finish();
1224 WARN_PRINT("Scan thread aborted...");
1225 set_process(false);
1226 }
1227
1228 if (filesystem) {
1229 memdelete(filesystem);
1230 }
1231 if (new_filesystem) {
1232 memdelete(new_filesystem);
1233 }
1234 filesystem = nullptr;
1235 new_filesystem = nullptr;
1236 } break;
1237
1238 case NOTIFICATION_PROCESS: {
1239 if (use_threads) {
1240 /** This hack exists because of the EditorProgress nature
1241 * of processing events recursively. This needs to be rewritten
1242 * at some point entirely, but in the meantime the following
1243 * hack prevents deadlock on import.
1244 */
1245
1246 static bool prevent_recursive_process_hack = false;
1247 if (prevent_recursive_process_hack) {
1248 break;
1249 }
1250
1251 prevent_recursive_process_hack = true;
1252
1253 bool done_importing = false;
1254
1255 if (scanning_changes) {
1256 if (scanning_changes_done) {
1257 set_process(false);
1258
1259 if (thread_sources.is_started()) {
1260 thread_sources.wait_to_finish();
1261 }
1262 bool changed = _update_scan_actions();
1263 _update_pending_script_classes();
1264 if (changed) {
1265 emit_signal(SNAME("filesystem_changed"));
1266 }
1267 emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
1268 first_scan = false;
1269 scanning_changes = false; // Changed to false here to prevent recursive triggering of scan thread.
1270 done_importing = true;
1271 }
1272 } else if (!scanning && thread.is_started()) {
1273 set_process(false);
1274
1275 if (filesystem) {
1276 memdelete(filesystem);
1277 }
1278 filesystem = new_filesystem;
1279 new_filesystem = nullptr;
1280 thread.wait_to_finish();
1281 _update_scan_actions();
1282 _update_pending_script_classes();
1283 emit_signal(SNAME("filesystem_changed"));
1284 emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
1285 first_scan = false;
1286 }
1287
1288 if (done_importing && scan_changes_pending) {
1289 scan_changes_pending = false;
1290 scan_changes();
1291 }
1292
1293 prevent_recursive_process_hack = false;
1294 }
1295 } break;
1296 }
1297}
1298
1299bool EditorFileSystem::is_scanning() const {
1300 return scanning || scanning_changes;
1301}
1302
1303float EditorFileSystem::get_scanning_progress() const {
1304 return scan_total;
1305}
1306
1307EditorFileSystemDirectory *EditorFileSystem::get_filesystem() {
1308 return filesystem;
1309}
1310
1311void EditorFileSystem::_save_filesystem_cache(EditorFileSystemDirectory *p_dir, Ref<FileAccess> p_file) {
1312 if (!p_dir) {
1313 return; //none
1314 }
1315 p_file->store_line("::" + p_dir->get_path() + "::" + String::num(p_dir->modified_time));
1316
1317 for (int i = 0; i < p_dir->files.size(); i++) {
1318 if (!p_dir->files[i]->import_group_file.is_empty()) {
1319 group_file_cache.insert(p_dir->files[i]->import_group_file);
1320 }
1321
1322 String type = p_dir->files[i]->type;
1323 if (p_dir->files[i]->resource_script_class) {
1324 type += "/" + String(p_dir->files[i]->resource_script_class);
1325 }
1326 String s = p_dir->files[i]->file + "::" + type + "::" + itos(p_dir->files[i]->uid) + "::" + itos(p_dir->files[i]->modified_time) + "::" + itos(p_dir->files[i]->import_modified_time) + "::" + itos(p_dir->files[i]->import_valid) + "::" + p_dir->files[i]->import_group_file + "::" + p_dir->files[i]->script_class_name + "<>" + p_dir->files[i]->script_class_extends + "<>" + p_dir->files[i]->script_class_icon_path;
1327 s += "::";
1328 for (int j = 0; j < p_dir->files[i]->deps.size(); j++) {
1329 if (j > 0) {
1330 s += "<>";
1331 }
1332 s += p_dir->files[i]->deps[j];
1333 }
1334
1335 p_file->store_line(s);
1336 }
1337
1338 for (int i = 0; i < p_dir->subdirs.size(); i++) {
1339 _save_filesystem_cache(p_dir->subdirs[i], p_file);
1340 }
1341}
1342
1343bool EditorFileSystem::_find_file(const String &p_file, EditorFileSystemDirectory **r_d, int &r_file_pos) const {
1344 //todo make faster
1345
1346 if (!filesystem || scanning) {
1347 return false;
1348 }
1349
1350 String f = ProjectSettings::get_singleton()->localize_path(p_file);
1351
1352 if (!f.begins_with("res://")) {
1353 return false;
1354 }
1355 f = f.substr(6, f.length());
1356 f = f.replace("\\", "/");
1357
1358 Vector<String> path = f.split("/");
1359
1360 if (path.size() == 0) {
1361 return false;
1362 }
1363 String file = path[path.size() - 1];
1364 path.resize(path.size() - 1);
1365
1366 EditorFileSystemDirectory *fs = filesystem;
1367
1368 for (int i = 0; i < path.size(); i++) {
1369 if (path[i].begins_with(".")) {
1370 return false;
1371 }
1372
1373 int idx = -1;
1374 for (int j = 0; j < fs->get_subdir_count(); j++) {
1375 if (fs->get_subdir(j)->get_name() == path[i]) {
1376 idx = j;
1377 break;
1378 }
1379 }
1380
1381 if (idx == -1) {
1382 //does not exist, create i guess?
1383 EditorFileSystemDirectory *efsd = memnew(EditorFileSystemDirectory);
1384
1385 efsd->name = path[i];
1386 efsd->parent = fs;
1387
1388 int idx2 = 0;
1389 for (int j = 0; j < fs->get_subdir_count(); j++) {
1390 if (efsd->name.naturalnocasecmp_to(fs->get_subdir(j)->get_name()) < 0) {
1391 break;
1392 }
1393 idx2++;
1394 }
1395
1396 if (idx2 == fs->get_subdir_count()) {
1397 fs->subdirs.push_back(efsd);
1398 } else {
1399 fs->subdirs.insert(idx2, efsd);
1400 }
1401 fs = efsd;
1402 } else {
1403 fs = fs->get_subdir(idx);
1404 }
1405 }
1406
1407 int cpos = -1;
1408 for (int i = 0; i < fs->files.size(); i++) {
1409 if (fs->files[i]->file == file) {
1410 cpos = i;
1411 break;
1412 }
1413 }
1414
1415 r_file_pos = cpos;
1416 *r_d = fs;
1417
1418 return cpos != -1;
1419}
1420
1421String EditorFileSystem::get_file_type(const String &p_file) const {
1422 EditorFileSystemDirectory *fs = nullptr;
1423 int cpos = -1;
1424
1425 if (!_find_file(p_file, &fs, cpos)) {
1426 return "";
1427 }
1428
1429 return fs->files[cpos]->type;
1430}
1431
1432EditorFileSystemDirectory *EditorFileSystem::find_file(const String &p_file, int *r_index) const {
1433 if (!filesystem || scanning) {
1434 return nullptr;
1435 }
1436
1437 EditorFileSystemDirectory *fs = nullptr;
1438 int cpos = -1;
1439 if (!_find_file(p_file, &fs, cpos)) {
1440 return nullptr;
1441 }
1442
1443 if (r_index) {
1444 *r_index = cpos;
1445 }
1446
1447 return fs;
1448}
1449
1450EditorFileSystemDirectory *EditorFileSystem::get_filesystem_path(const String &p_path) {
1451 if (!filesystem || scanning) {
1452 return nullptr;
1453 }
1454
1455 String f = ProjectSettings::get_singleton()->localize_path(p_path);
1456
1457 if (!f.begins_with("res://")) {
1458 return nullptr;
1459 }
1460
1461 f = f.substr(6, f.length());
1462 f = f.replace("\\", "/");
1463 if (f.is_empty()) {
1464 return filesystem;
1465 }
1466
1467 if (f.ends_with("/")) {
1468 f = f.substr(0, f.length() - 1);
1469 }
1470
1471 Vector<String> path = f.split("/");
1472
1473 if (path.size() == 0) {
1474 return nullptr;
1475 }
1476
1477 EditorFileSystemDirectory *fs = filesystem;
1478
1479 for (int i = 0; i < path.size(); i++) {
1480 int idx = -1;
1481 for (int j = 0; j < fs->get_subdir_count(); j++) {
1482 if (fs->get_subdir(j)->get_name() == path[i]) {
1483 idx = j;
1484 break;
1485 }
1486 }
1487
1488 if (idx == -1) {
1489 return nullptr;
1490 } else {
1491 fs = fs->get_subdir(idx);
1492 }
1493 }
1494
1495 return fs;
1496}
1497
1498void EditorFileSystem::_save_late_updated_files() {
1499 //files that already existed, and were modified, need re-scanning for dependencies upon project restart. This is done via saving this special file
1500 String fscache = EditorPaths::get_singleton()->get_project_settings_dir().path_join("filesystem_update4");
1501 Ref<FileAccess> f = FileAccess::open(fscache, FileAccess::WRITE);
1502 ERR_FAIL_COND_MSG(f.is_null(), "Cannot create file '" + fscache + "'. Check user write permissions.");
1503 for (const String &E : late_update_files) {
1504 f->store_line(E);
1505 }
1506}
1507
1508Vector<String> EditorFileSystem::_get_dependencies(const String &p_path) {
1509 // Avoid error spam on first opening of a not yet imported project by treating the following situation
1510 // as a benign one, not letting the file open error happen: the resource is of an importable type but
1511 // it has not been imported yet.
1512 if (ResourceFormatImporter::get_singleton()->recognize_path(p_path)) {
1513 const String &internal_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(p_path);
1514 if (!internal_path.is_empty() && !FileAccess::exists(internal_path)) { // If path is empty (error), keep the code flow to the error.
1515 return Vector<String>();
1516 }
1517 }
1518
1519 List<String> deps;
1520 ResourceLoader::get_dependencies(p_path, &deps);
1521
1522 Vector<String> ret;
1523 for (const String &E : deps) {
1524 ret.push_back(E);
1525 }
1526
1527 return ret;
1528}
1529
1530String EditorFileSystem::_get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const {
1531 for (int i = 0; i < ScriptServer::get_language_count(); i++) {
1532 if (ScriptServer::get_language(i)->handles_global_class_type(p_type)) {
1533 String global_name;
1534 String extends;
1535 String icon_path;
1536
1537 global_name = ScriptServer::get_language(i)->get_global_class_name(p_path, &extends, &icon_path);
1538 *r_extends = extends;
1539 *r_icon_path = icon_path;
1540 return global_name;
1541 }
1542 }
1543 *r_extends = String();
1544 *r_icon_path = String();
1545 return String();
1546}
1547
1548void EditorFileSystem::_update_script_classes() {
1549 update_script_mutex.lock();
1550
1551 for (const String &path : update_script_paths) {
1552 ScriptServer::remove_global_class_by_path(path); // First remove, just in case it changed
1553
1554 int index = -1;
1555 EditorFileSystemDirectory *efd = find_file(path, &index);
1556
1557 if (!efd || index < 0) {
1558 // The file was removed
1559 continue;
1560 }
1561
1562 if (!efd->files[index]->script_class_name.is_empty()) {
1563 String lang;
1564 for (int j = 0; j < ScriptServer::get_language_count(); j++) {
1565 if (ScriptServer::get_language(j)->handles_global_class_type(efd->files[index]->type)) {
1566 lang = ScriptServer::get_language(j)->get_name();
1567 }
1568 }
1569 if (lang.is_empty()) {
1570 continue; // No lang found that can handle this global class
1571 }
1572
1573 ScriptServer::add_global_class(efd->files[index]->script_class_name, efd->files[index]->script_class_extends, lang, path);
1574 EditorNode::get_editor_data().script_class_set_icon_path(efd->files[index]->script_class_name, efd->files[index]->script_class_icon_path);
1575 EditorNode::get_editor_data().script_class_set_name(path, efd->files[index]->script_class_name);
1576 }
1577 }
1578
1579 // Parse documentation second, as it requires the class names to be correct and registered
1580 for (const String &path : update_script_paths) {
1581 int index = -1;
1582 EditorFileSystemDirectory *efd = find_file(path, &index);
1583
1584 if (!efd || index < 0) {
1585 // The file was removed
1586 continue;
1587 }
1588
1589 for (int i = 0; i < ScriptServer::get_language_count(); i++) {
1590 ScriptLanguage *lang = ScriptServer::get_language(i);
1591 if (lang->supports_documentation() && efd->files[index]->type == lang->get_type()) {
1592 Ref<Script> scr = ResourceLoader::load(path);
1593 if (scr.is_null()) {
1594 continue;
1595 }
1596 Vector<DocData::ClassDoc> docs = scr->get_documentation();
1597 for (int j = 0; j < docs.size(); j++) {
1598 EditorHelp::get_doc_data()->add_doc(docs[j]);
1599 }
1600 }
1601 }
1602 }
1603
1604 update_script_paths.clear();
1605 update_script_mutex.unlock();
1606
1607 ScriptServer::save_global_classes();
1608 EditorNode::get_editor_data().script_class_save_icon_paths();
1609 emit_signal("script_classes_updated");
1610
1611 // Rescan custom loaders and savers.
1612 // Doing the following here because the `filesystem_changed` signal fires multiple times and isn't always followed by script classes update.
1613 // So I thought it's better to do this when script classes really get updated
1614 ResourceLoader::remove_custom_loaders();
1615 ResourceLoader::add_custom_loaders();
1616 ResourceSaver::remove_custom_savers();
1617 ResourceSaver::add_custom_savers();
1618}
1619
1620void EditorFileSystem::_update_pending_script_classes() {
1621 if (!update_script_paths.is_empty()) {
1622 _update_script_classes();
1623 } else {
1624 // In case the class cache file was removed somehow, regenerate it.
1625 if (!FileAccess::exists(ScriptServer::get_global_class_cache_file_path())) {
1626 ScriptServer::save_global_classes();
1627 }
1628 }
1629}
1630
1631void EditorFileSystem::_queue_update_script_class(const String &p_path) {
1632 update_script_mutex.lock();
1633 update_script_paths.insert(p_path);
1634 update_script_mutex.unlock();
1635}
1636
1637void EditorFileSystem::update_file(const String &p_file) {
1638 ERR_FAIL_COND(p_file.is_empty());
1639 EditorFileSystemDirectory *fs = nullptr;
1640 int cpos = -1;
1641
1642 if (!_find_file(p_file, &fs, cpos)) {
1643 if (!fs) {
1644 return;
1645 }
1646 }
1647
1648 if (!FileAccess::exists(p_file)) {
1649 //was removed
1650 _delete_internal_files(p_file);
1651 if (cpos != -1) { // Might've never been part of the editor file system (*.* files deleted in Open dialog).
1652 if (fs->files[cpos]->uid != ResourceUID::INVALID_ID) {
1653 if (ResourceUID::get_singleton()->has_id(fs->files[cpos]->uid)) {
1654 ResourceUID::get_singleton()->remove_id(fs->files[cpos]->uid);
1655 }
1656 }
1657 if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) {
1658 _queue_update_script_class(p_file);
1659 }
1660
1661 memdelete(fs->files[cpos]);
1662 fs->files.remove_at(cpos);
1663 }
1664
1665 _update_pending_script_classes();
1666 call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later
1667 return;
1668 }
1669
1670 String type = ResourceLoader::get_resource_type(p_file);
1671 if (type.is_empty() && textfile_extensions.has(p_file.get_extension())) {
1672 type = "TextFile";
1673 }
1674 String script_class = ResourceLoader::get_resource_script_class(p_file);
1675
1676 ResourceUID::ID uid = ResourceLoader::get_resource_uid(p_file);
1677
1678 if (cpos == -1) {
1679 // The file did not exist, it was added.
1680 int idx = 0;
1681 String file_name = p_file.get_file();
1682
1683 for (int i = 0; i < fs->files.size(); i++) {
1684 if (p_file.naturalnocasecmp_to(fs->files[i]->file) < 0) {
1685 break;
1686 }
1687 idx++;
1688 }
1689
1690 EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo);
1691 fi->file = file_name;
1692 fi->import_modified_time = 0;
1693 fi->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file);
1694
1695 if (idx == fs->files.size()) {
1696 fs->files.push_back(fi);
1697 } else {
1698 fs->files.insert(idx, fi);
1699 }
1700 cpos = idx;
1701 } else {
1702 //the file exists and it was updated, and was not added in this step.
1703 //this means we must force upon next restart to scan it again, to get proper type and dependencies
1704 late_update_files.insert(p_file);
1705 _save_late_updated_files(); //files need to be updated in the re-scan
1706 }
1707
1708 fs->files[cpos]->type = type;
1709 fs->files[cpos]->resource_script_class = script_class;
1710 fs->files[cpos]->uid = uid;
1711 fs->files[cpos]->script_class_name = _get_global_script_class(type, p_file, &fs->files[cpos]->script_class_extends, &fs->files[cpos]->script_class_icon_path);
1712 fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(p_file);
1713 fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
1714 fs->files[cpos]->deps = _get_dependencies(p_file);
1715 fs->files[cpos]->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file);
1716
1717 if (uid != ResourceUID::INVALID_ID) {
1718 if (ResourceUID::get_singleton()->has_id(uid)) {
1719 ResourceUID::get_singleton()->set_id(uid, p_file);
1720 } else {
1721 ResourceUID::get_singleton()->add_id(uid, p_file);
1722 }
1723
1724 ResourceUID::get_singleton()->update_cache();
1725 }
1726 // Update preview
1727 EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
1728
1729 if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) {
1730 _queue_update_script_class(p_file);
1731 }
1732
1733 _update_pending_script_classes();
1734 call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later
1735}
1736
1737HashSet<String> EditorFileSystem::get_valid_extensions() const {
1738 return valid_extensions;
1739}
1740
1741Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector<String> &p_files) {
1742 String importer_name;
1743
1744 HashMap<String, HashMap<StringName, Variant>> source_file_options;
1745 HashMap<String, ResourceUID::ID> uids;
1746 HashMap<String, String> base_paths;
1747 for (int i = 0; i < p_files.size(); i++) {
1748 Ref<ConfigFile> config;
1749 config.instantiate();
1750 Error err = config->load(p_files[i] + ".import");
1751 ERR_CONTINUE(err != OK);
1752 ERR_CONTINUE(!config->has_section_key("remap", "importer"));
1753 String file_importer_name = config->get_value("remap", "importer");
1754 ERR_CONTINUE(file_importer_name.is_empty());
1755
1756 if (!importer_name.is_empty() && importer_name != file_importer_name) {
1757 EditorNode::get_singleton()->show_warning(vformat(TTR("There are multiple importers for different types pointing to file %s, import aborted"), p_group_file));
1758 ERR_FAIL_V(ERR_FILE_CORRUPT);
1759 }
1760
1761 ResourceUID::ID uid = ResourceUID::INVALID_ID;
1762
1763 if (config->has_section_key("remap", "uid")) {
1764 String uidt = config->get_value("remap", "uid");
1765 uid = ResourceUID::get_singleton()->text_to_id(uidt);
1766 }
1767
1768 uids[p_files[i]] = uid;
1769
1770 source_file_options[p_files[i]] = HashMap<StringName, Variant>();
1771 importer_name = file_importer_name;
1772
1773 if (importer_name == "keep") {
1774 continue; //do nothing
1775 }
1776
1777 Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name);
1778 ERR_FAIL_COND_V(!importer.is_valid(), ERR_FILE_CORRUPT);
1779 List<ResourceImporter::ImportOption> options;
1780 importer->get_import_options(p_files[i], &options);
1781 //set default values
1782 for (const ResourceImporter::ImportOption &E : options) {
1783 source_file_options[p_files[i]][E.option.name] = E.default_value;
1784 }
1785
1786 if (config->has_section("params")) {
1787 List<String> sk;
1788 config->get_section_keys("params", &sk);
1789 for (const String &param : sk) {
1790 Variant value = config->get_value("params", param);
1791 //override with whatever is in file
1792 source_file_options[p_files[i]][param] = value;
1793 }
1794 }
1795
1796 base_paths[p_files[i]] = ResourceFormatImporter::get_singleton()->get_import_base_path(p_files[i]);
1797 }
1798
1799 if (importer_name == "keep") {
1800 return OK; // (do nothing)
1801 }
1802
1803 ERR_FAIL_COND_V(importer_name.is_empty(), ERR_UNCONFIGURED);
1804
1805 Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name);
1806
1807 Error err = importer->import_group_file(p_group_file, source_file_options, base_paths);
1808
1809 //all went well, overwrite config files with proper remaps and md5s
1810 for (const KeyValue<String, HashMap<StringName, Variant>> &E : source_file_options) {
1811 const String &file = E.key;
1812 String base_path = ResourceFormatImporter::get_singleton()->get_import_base_path(file);
1813 Vector<String> dest_paths;
1814 ResourceUID::ID uid = uids[file];
1815 {
1816 Ref<FileAccess> f = FileAccess::open(file + ".import", FileAccess::WRITE);
1817 ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Cannot open import file '" + file + ".import'.");
1818
1819 //write manually, as order matters ([remap] has to go first for performance).
1820 f->store_line("[remap]");
1821 f->store_line("");
1822 f->store_line("importer=\"" + importer->get_importer_name() + "\"");
1823 int version = importer->get_format_version();
1824 if (version > 0) {
1825 f->store_line("importer_version=" + itos(version));
1826 }
1827 if (!importer->get_resource_type().is_empty()) {
1828 f->store_line("type=\"" + importer->get_resource_type() + "\"");
1829 }
1830
1831 if (uid == ResourceUID::INVALID_ID) {
1832 uid = ResourceUID::get_singleton()->create_id();
1833 }
1834
1835 f->store_line("uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\""); // Store in readable format.
1836
1837 if (err == OK) {
1838 String path = base_path + "." + importer->get_save_extension();
1839 f->store_line("path=\"" + path + "\"");
1840 dest_paths.push_back(path);
1841 }
1842
1843 f->store_line("group_file=" + Variant(p_group_file).get_construct_string());
1844
1845 if (err == OK) {
1846 f->store_line("valid=true");
1847 } else {
1848 f->store_line("valid=false");
1849 }
1850 f->store_line("[deps]\n");
1851
1852 f->store_line("");
1853
1854 f->store_line("source_file=" + Variant(file).get_construct_string());
1855 if (dest_paths.size()) {
1856 Array dp;
1857 for (int i = 0; i < dest_paths.size(); i++) {
1858 dp.push_back(dest_paths[i]);
1859 }
1860 f->store_line("dest_files=" + Variant(dp).get_construct_string() + "\n");
1861 }
1862 f->store_line("[params]");
1863 f->store_line("");
1864
1865 //store options in provided order, to avoid file changing. Order is also important because first match is accepted first.
1866
1867 List<ResourceImporter::ImportOption> options;
1868 importer->get_import_options(file, &options);
1869 //set default values
1870 for (const ResourceImporter::ImportOption &F : options) {
1871 String base = F.option.name;
1872 Variant v = F.default_value;
1873 if (source_file_options[file].has(base)) {
1874 v = source_file_options[file][base];
1875 }
1876 String value;
1877 VariantWriter::write_to_string(v, value);
1878 f->store_line(base + "=" + value);
1879 }
1880 }
1881
1882 // Store the md5's of the various files. These are stored separately so that the .import files can be version controlled.
1883 {
1884 Ref<FileAccess> md5s = FileAccess::open(base_path + ".md5", FileAccess::WRITE);
1885 ERR_FAIL_COND_V_MSG(md5s.is_null(), ERR_FILE_CANT_OPEN, "Cannot open MD5 file '" + base_path + ".md5'.");
1886
1887 md5s->store_line("source_md5=\"" + FileAccess::get_md5(file) + "\"");
1888 if (dest_paths.size()) {
1889 md5s->store_line("dest_md5=\"" + FileAccess::get_multiple_md5(dest_paths) + "\"\n");
1890 }
1891 }
1892
1893 EditorFileSystemDirectory *fs = nullptr;
1894 int cpos = -1;
1895 bool found = _find_file(file, &fs, cpos);
1896 ERR_FAIL_COND_V_MSG(!found, ERR_UNCONFIGURED, "Can't find file '" + file + "'.");
1897
1898 //update modified times, to avoid reimport
1899 fs->files[cpos]->modified_time = FileAccess::get_modified_time(file);
1900 fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(file + ".import");
1901 fs->files[cpos]->deps = _get_dependencies(file);
1902 fs->files[cpos]->uid = uid;
1903 fs->files[cpos]->type = importer->get_resource_type();
1904 if (fs->files[cpos]->type == "" && textfile_extensions.has(file.get_extension())) {
1905 fs->files[cpos]->type = "TextFile";
1906 }
1907 fs->files[cpos]->import_valid = err == OK;
1908
1909 if (ResourceUID::get_singleton()->has_id(uid)) {
1910 ResourceUID::get_singleton()->set_id(uid, file);
1911 } else {
1912 ResourceUID::get_singleton()->add_id(uid, file);
1913 }
1914
1915 //if file is currently up, maybe the source it was loaded from changed, so import math must be updated for it
1916 //to reload properly
1917 Ref<Resource> r = ResourceCache::get_ref(file);
1918
1919 if (r.is_valid()) {
1920 if (!r->get_import_path().is_empty()) {
1921 String dst_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(file);
1922 r->set_import_path(dst_path);
1923 r->set_import_last_modified_time(0);
1924 }
1925 }
1926
1927 EditorResourcePreview::get_singleton()->check_for_invalidation(file);
1928 }
1929
1930 return err;
1931}
1932
1933Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant *p_generator_parameters) {
1934 EditorFileSystemDirectory *fs = nullptr;
1935 int cpos = -1;
1936 bool found = _find_file(p_file, &fs, cpos);
1937 ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, "Can't find file '" + p_file + "'.");
1938
1939 //try to obtain existing params
1940
1941 HashMap<StringName, Variant> params = p_custom_options;
1942 String importer_name; //empty by default though
1943
1944 if (!p_custom_importer.is_empty()) {
1945 importer_name = p_custom_importer;
1946 }
1947
1948 ResourceUID::ID uid = ResourceUID::INVALID_ID;
1949 Variant generator_parameters;
1950 if (p_generator_parameters) {
1951 generator_parameters = *p_generator_parameters;
1952 }
1953
1954 if (FileAccess::exists(p_file + ".import")) {
1955 //use existing
1956 Ref<ConfigFile> cf;
1957 cf.instantiate();
1958 Error err = cf->load(p_file + ".import");
1959 if (err == OK) {
1960 if (cf->has_section("params")) {
1961 List<String> sk;
1962 cf->get_section_keys("params", &sk);
1963 for (const String &E : sk) {
1964 if (!params.has(E)) {
1965 params[E] = cf->get_value("params", E);
1966 }
1967 }
1968 }
1969
1970 if (cf->has_section("remap")) {
1971 if (p_custom_importer.is_empty()) {
1972 importer_name = cf->get_value("remap", "importer");
1973 }
1974
1975 if (cf->has_section_key("remap", "uid")) {
1976 String uidt = cf->get_value("remap", "uid");
1977 uid = ResourceUID::get_singleton()->text_to_id(uidt);
1978 }
1979
1980 if (!p_generator_parameters) {
1981 if (cf->has_section_key("remap", "generator_parameters")) {
1982 generator_parameters = cf->get_value("remap", "generator_parameters");
1983 }
1984 }
1985 }
1986 }
1987 }
1988
1989 if (importer_name == "keep") {
1990 //keep files, do nothing.
1991 fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
1992 fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
1993 fs->files[cpos]->deps.clear();
1994 fs->files[cpos]->type = "";
1995 fs->files[cpos]->import_valid = false;
1996 EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
1997 return OK;
1998 }
1999 Ref<ResourceImporter> importer;
2000 bool load_default = false;
2001 //find the importer
2002 if (!importer_name.is_empty()) {
2003 importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name);
2004 }
2005
2006 if (importer.is_null()) {
2007 //not found by name, find by extension
2008 importer = ResourceFormatImporter::get_singleton()->get_importer_by_extension(p_file.get_extension());
2009 load_default = true;
2010 if (importer.is_null()) {
2011 ERR_FAIL_V_MSG(ERR_FILE_CANT_OPEN, "BUG: File queued for import, but can't be imported, importer for type '" + importer_name + "' not found.");
2012 }
2013 }
2014
2015 //mix with default params, in case a parameter is missing
2016
2017 List<ResourceImporter::ImportOption> opts;
2018 importer->get_import_options(p_file, &opts);
2019 for (const ResourceImporter::ImportOption &E : opts) {
2020 if (!params.has(E.option.name)) { //this one is not present
2021 params[E.option.name] = E.default_value;
2022 }
2023 }
2024
2025 if (load_default && ProjectSettings::get_singleton()->has_setting("importer_defaults/" + importer->get_importer_name())) {
2026 //use defaults if exist
2027 Dictionary d = GLOBAL_GET("importer_defaults/" + importer->get_importer_name());
2028 List<Variant> v;
2029 d.get_key_list(&v);
2030
2031 for (const Variant &E : v) {
2032 params[E] = d[E];
2033 }
2034 }
2035
2036 //finally, perform import!!
2037 String base_path = ResourceFormatImporter::get_singleton()->get_import_base_path(p_file);
2038
2039 List<String> import_variants;
2040 List<String> gen_files;
2041 Variant meta;
2042 Error err = importer->import(p_file, base_path, params, &import_variants, &gen_files, &meta);
2043
2044 ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_UNRECOGNIZED, "Error importing '" + p_file + "'.");
2045
2046 //as import is complete, save the .import file
2047
2048 Vector<String> dest_paths;
2049 {
2050 Ref<FileAccess> f = FileAccess::open(p_file + ".import", FileAccess::WRITE);
2051 ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Cannot open file from path '" + p_file + ".import'.");
2052
2053 //write manually, as order matters ([remap] has to go first for performance).
2054 f->store_line("[remap]");
2055 f->store_line("");
2056 f->store_line("importer=\"" + importer->get_importer_name() + "\"");
2057 int version = importer->get_format_version();
2058 if (version > 0) {
2059 f->store_line("importer_version=" + itos(version));
2060 }
2061 if (!importer->get_resource_type().is_empty()) {
2062 f->store_line("type=\"" + importer->get_resource_type() + "\"");
2063 }
2064
2065 if (uid == ResourceUID::INVALID_ID) {
2066 uid = ResourceUID::get_singleton()->create_id();
2067 }
2068
2069 f->store_line("uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\""); //store in readable format
2070
2071 if (err == OK) {
2072 if (importer->get_save_extension().is_empty()) {
2073 //no path
2074 } else if (import_variants.size()) {
2075 //import with variants
2076 for (const String &E : import_variants) {
2077 String path = base_path.c_escape() + "." + E + "." + importer->get_save_extension();
2078
2079 f->store_line("path." + E + "=\"" + path + "\"");
2080 dest_paths.push_back(path);
2081 }
2082 } else {
2083 String path = base_path + "." + importer->get_save_extension();
2084 f->store_line("path=\"" + path + "\"");
2085 dest_paths.push_back(path);
2086 }
2087
2088 } else {
2089 f->store_line("valid=false");
2090 }
2091
2092 if (meta != Variant()) {
2093 f->store_line("metadata=" + meta.get_construct_string());
2094 }
2095
2096 if (generator_parameters != Variant()) {
2097 f->store_line("generator_parameters=" + generator_parameters.get_construct_string());
2098 }
2099
2100 f->store_line("");
2101
2102 f->store_line("[deps]\n");
2103
2104 if (gen_files.size()) {
2105 Array genf;
2106 for (const String &E : gen_files) {
2107 genf.push_back(E);
2108 dest_paths.push_back(E);
2109 }
2110
2111 String value;
2112 VariantWriter::write_to_string(genf, value);
2113 f->store_line("files=" + value);
2114 f->store_line("");
2115 }
2116
2117 f->store_line("source_file=" + Variant(p_file).get_construct_string());
2118
2119 if (dest_paths.size()) {
2120 Array dp;
2121 for (int i = 0; i < dest_paths.size(); i++) {
2122 dp.push_back(dest_paths[i]);
2123 }
2124 f->store_line("dest_files=" + Variant(dp).get_construct_string() + "\n");
2125 }
2126
2127 f->store_line("[params]");
2128 f->store_line("");
2129
2130 //store options in provided order, to avoid file changing. Order is also important because first match is accepted first.
2131
2132 for (const ResourceImporter::ImportOption &E : opts) {
2133 String base = E.option.name;
2134 String value;
2135 VariantWriter::write_to_string(params[base], value);
2136 f->store_line(base + "=" + value);
2137 }
2138 }
2139
2140 // Store the md5's of the various files. These are stored separately so that the .import files can be version controlled.
2141 {
2142 Ref<FileAccess> md5s = FileAccess::open(base_path + ".md5", FileAccess::WRITE);
2143 ERR_FAIL_COND_V_MSG(md5s.is_null(), ERR_FILE_CANT_OPEN, "Cannot open MD5 file '" + base_path + ".md5'.");
2144
2145 md5s->store_line("source_md5=\"" + FileAccess::get_md5(p_file) + "\"");
2146 if (dest_paths.size()) {
2147 md5s->store_line("dest_md5=\"" + FileAccess::get_multiple_md5(dest_paths) + "\"\n");
2148 }
2149 }
2150
2151 //update modified times, to avoid reimport
2152 fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
2153 fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
2154 fs->files[cpos]->deps = _get_dependencies(p_file);
2155 fs->files[cpos]->type = importer->get_resource_type();
2156 fs->files[cpos]->uid = uid;
2157 fs->files[cpos]->import_valid = fs->files[cpos]->type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file);
2158
2159 if (ResourceUID::get_singleton()->has_id(uid)) {
2160 ResourceUID::get_singleton()->set_id(uid, p_file);
2161 } else {
2162 ResourceUID::get_singleton()->add_id(uid, p_file);
2163 }
2164
2165 //if file is currently up, maybe the source it was loaded from changed, so import math must be updated for it
2166 //to reload properly
2167 Ref<Resource> r = ResourceCache::get_ref(p_file);
2168 if (r.is_valid()) {
2169 if (!r->get_import_path().is_empty()) {
2170 String dst_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(p_file);
2171 r->set_import_path(dst_path);
2172 r->set_import_last_modified_time(0);
2173 }
2174 }
2175
2176 EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
2177
2178 return OK;
2179}
2180
2181void EditorFileSystem::_find_group_files(EditorFileSystemDirectory *efd, HashMap<String, Vector<String>> &group_files, HashSet<String> &groups_to_reimport) {
2182 int fc = efd->files.size();
2183 const EditorFileSystemDirectory::FileInfo *const *files = efd->files.ptr();
2184 for (int i = 0; i < fc; i++) {
2185 if (groups_to_reimport.has(files[i]->import_group_file)) {
2186 if (!group_files.has(files[i]->import_group_file)) {
2187 group_files[files[i]->import_group_file] = Vector<String>();
2188 }
2189 group_files[files[i]->import_group_file].push_back(efd->get_file_path(i));
2190 }
2191 }
2192
2193 for (int i = 0; i < efd->get_subdir_count(); i++) {
2194 _find_group_files(efd->get_subdir(i), group_files, groups_to_reimport);
2195 }
2196}
2197
2198void EditorFileSystem::reimport_file_with_custom_parameters(const String &p_file, const String &p_importer, const HashMap<StringName, Variant> &p_custom_params) {
2199 _reimport_file(p_file, p_custom_params, p_importer);
2200
2201 // Emit the resource_reimported signal for the single file we just reimported.
2202 Vector<String> reloads;
2203 reloads.append(p_file);
2204 emit_signal(SNAME("resources_reimported"), reloads);
2205}
2206
2207void EditorFileSystem::_reimport_thread(uint32_t p_index, ImportThreadData *p_import_data) {
2208 p_import_data->max_index = MAX(p_import_data->reimport_from + int(p_index), p_import_data->max_index);
2209 _reimport_file(p_import_data->reimport_files[p_import_data->reimport_from + p_index].path);
2210}
2211
2212void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
2213 ERR_FAIL_COND_MSG(importing, "Attempted to call reimport_files() recursively, this is not allowed.");
2214 importing = true;
2215
2216 Vector<String> reloads;
2217
2218 EditorProgress pr("reimport", TTR("(Re)Importing Assets"), p_files.size());
2219
2220 Vector<ImportFile> reimport_files;
2221
2222 HashSet<String> groups_to_reimport;
2223
2224 for (int i = 0; i < p_files.size(); i++) {
2225 String file = p_files[i];
2226
2227 ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(file);
2228 if (uid != ResourceUID::INVALID_ID && ResourceUID::get_singleton()->has_id(uid)) {
2229 file = ResourceUID::get_singleton()->get_id_path(uid);
2230 }
2231
2232 String group_file = ResourceFormatImporter::get_singleton()->get_import_group_file(file);
2233
2234 if (group_file_cache.has(file)) {
2235 // Maybe the file itself is a group!
2236 groups_to_reimport.insert(file);
2237 // Groups do not belong to groups.
2238 group_file = String();
2239 } else if (groups_to_reimport.has(file)) {
2240 // Groups do not belong to groups.
2241 group_file = String();
2242 } else if (!group_file.is_empty()) {
2243 // It's a group file, add group to import and skip this file.
2244 groups_to_reimport.insert(group_file);
2245 } else {
2246 // It's a regular file.
2247 ImportFile ifile;
2248 ifile.path = file;
2249 ResourceFormatImporter::get_singleton()->get_import_order_threads_and_importer(file, ifile.order, ifile.threaded, ifile.importer);
2250 reloads.push_back(file);
2251 reimport_files.push_back(ifile);
2252 }
2253
2254 // Group may have changed, so also update group reference.
2255 EditorFileSystemDirectory *fs = nullptr;
2256 int cpos = -1;
2257 if (_find_file(file, &fs, cpos)) {
2258 fs->files.write[cpos]->import_group_file = group_file;
2259 }
2260 }
2261
2262 reimport_files.sort();
2263
2264 bool use_multiple_threads = GLOBAL_GET("editor/import/use_multiple_threads");
2265
2266 int from = 0;
2267 for (int i = 0; i < reimport_files.size(); i++) {
2268 if (groups_to_reimport.has(reimport_files[i].path)) {
2269 continue;
2270 }
2271
2272 if (use_multiple_threads && reimport_files[i].threaded) {
2273 if (i + 1 == reimport_files.size() || reimport_files[i + 1].importer != reimport_files[from].importer) {
2274 if (from - i == 0) {
2275 // Single file, do not use threads.
2276 pr.step(reimport_files[i].path.get_file(), i);
2277 _reimport_file(reimport_files[i].path);
2278 } else {
2279 Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(reimport_files[from].importer);
2280 ERR_CONTINUE(!importer.is_valid());
2281
2282 importer->import_threaded_begin();
2283
2284 ImportThreadData tdata;
2285 tdata.max_index = from;
2286 tdata.reimport_from = from;
2287 tdata.reimport_files = reimport_files.ptr();
2288
2289 WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &EditorFileSystem::_reimport_thread, &tdata, i - from + 1, -1, false, vformat(TTR("Import resources of type: %s"), reimport_files[from].importer));
2290 int current_index = from - 1;
2291 do {
2292 if (current_index < tdata.max_index) {
2293 current_index = tdata.max_index;
2294 pr.step(reimport_files[current_index].path.get_file(), current_index);
2295 }
2296 OS::get_singleton()->delay_usec(1);
2297 } while (!WorkerThreadPool::get_singleton()->is_group_task_completed(group_task));
2298
2299 WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
2300
2301 importer->import_threaded_end();
2302 }
2303
2304 from = i + 1;
2305 }
2306
2307 } else {
2308 pr.step(reimport_files[i].path.get_file(), i);
2309 _reimport_file(reimport_files[i].path);
2310 }
2311 }
2312
2313 // Reimport groups.
2314
2315 from = reimport_files.size();
2316
2317 if (groups_to_reimport.size()) {
2318 HashMap<String, Vector<String>> group_files;
2319 _find_group_files(filesystem, group_files, groups_to_reimport);
2320 for (const KeyValue<String, Vector<String>> &E : group_files) {
2321 pr.step(E.key.get_file(), from++);
2322 Error err = _reimport_group(E.key, E.value);
2323 reloads.push_back(E.key);
2324 reloads.append_array(E.value);
2325 if (err == OK) {
2326 _reimport_file(E.key);
2327 }
2328 }
2329 }
2330
2331 ResourceUID::get_singleton()->update_cache(); // After reimporting, update the cache.
2332
2333 _save_filesystem_cache();
2334 _update_pending_script_classes();
2335 importing = false;
2336 if (!is_scanning()) {
2337 emit_signal(SNAME("filesystem_changed"));
2338 }
2339
2340 emit_signal(SNAME("resources_reimported"), reloads);
2341}
2342
2343Error EditorFileSystem::reimport_append(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) {
2344 ERR_FAIL_COND_V_MSG(!importing, ERR_INVALID_PARAMETER, "Can only append files to import during a current reimport process.");
2345 return _reimport_file(p_file, p_custom_options, p_custom_importer, &p_generator_parameters);
2346}
2347
2348Error EditorFileSystem::_resource_import(const String &p_path) {
2349 Vector<String> files;
2350 files.push_back(p_path);
2351
2352 singleton->update_file(p_path);
2353 singleton->reimport_files(files);
2354
2355 return OK;
2356}
2357
2358bool EditorFileSystem::_should_skip_directory(const String &p_path) {
2359 String project_data_path = ProjectSettings::get_singleton()->get_project_data_path();
2360 if (p_path == project_data_path || p_path.begins_with(project_data_path + "/")) {
2361 return true;
2362 }
2363
2364 if (FileAccess::exists(p_path.path_join("project.godot"))) {
2365 // Skip if another project inside this.
2366 WARN_PRINT_ONCE(vformat("Detected another project.godot at %s. The folder will be ignored.", p_path));
2367 return true;
2368 }
2369
2370 if (FileAccess::exists(p_path.path_join(".gdignore"))) {
2371 // Skip if a `.gdignore` file is inside this.
2372 return true;
2373 }
2374
2375 return false;
2376}
2377
2378bool EditorFileSystem::is_group_file(const String &p_path) const {
2379 return group_file_cache.has(p_path);
2380}
2381
2382void EditorFileSystem::_move_group_files(EditorFileSystemDirectory *efd, const String &p_group_file, const String &p_new_location) {
2383 int fc = efd->files.size();
2384 EditorFileSystemDirectory::FileInfo *const *files = efd->files.ptrw();
2385 for (int i = 0; i < fc; i++) {
2386 if (files[i]->import_group_file == p_group_file) {
2387 files[i]->import_group_file = p_new_location;
2388
2389 Ref<ConfigFile> config;
2390 config.instantiate();
2391 String path = efd->get_file_path(i) + ".import";
2392 Error err = config->load(path);
2393 if (err != OK) {
2394 continue;
2395 }
2396 if (config->has_section_key("remap", "group_file")) {
2397 config->set_value("remap", "group_file", p_new_location);
2398 }
2399
2400 List<String> sk;
2401 config->get_section_keys("params", &sk);
2402 for (const String &param : sk) {
2403 //not very clean, but should work
2404 String value = config->get_value("params", param);
2405 if (value == p_group_file) {
2406 config->set_value("params", param, p_new_location);
2407 }
2408 }
2409
2410 config->save(path);
2411 }
2412 }
2413
2414 for (int i = 0; i < efd->get_subdir_count(); i++) {
2415 _move_group_files(efd->get_subdir(i), p_group_file, p_new_location);
2416 }
2417}
2418
2419void EditorFileSystem::move_group_file(const String &p_path, const String &p_new_path) {
2420 if (get_filesystem()) {
2421 _move_group_files(get_filesystem(), p_path, p_new_path);
2422 if (group_file_cache.has(p_path)) {
2423 group_file_cache.erase(p_path);
2424 group_file_cache.insert(p_new_path);
2425 }
2426 }
2427}
2428
2429ResourceUID::ID EditorFileSystem::_resource_saver_get_resource_id_for_path(const String &p_path, bool p_generate) {
2430 if (!p_path.is_resource_file() || p_path.begins_with(ProjectSettings::get_singleton()->get_project_data_path())) {
2431 // Saved externally (configuration file) or internal file, do not assign an ID.
2432 return ResourceUID::INVALID_ID;
2433 }
2434
2435 EditorFileSystemDirectory *fs = nullptr;
2436 int cpos = -1;
2437
2438 if (!singleton->_find_file(p_path, &fs, cpos)) {
2439 // Fallback to ResourceLoader if filesystem cache fails (can happen during scanning etc.).
2440 ResourceUID::ID fallback = ResourceLoader::get_resource_uid(p_path);
2441 if (fallback != ResourceUID::INVALID_ID) {
2442 return fallback;
2443 }
2444
2445 if (p_generate) {
2446 return ResourceUID::get_singleton()->create_id(); // Just create a new one, we will be notified of save anyway and fetch the right UID at that time, to keep things simple.
2447 } else {
2448 return ResourceUID::INVALID_ID;
2449 }
2450 } else if (fs->files[cpos]->uid != ResourceUID::INVALID_ID) {
2451 return fs->files[cpos]->uid;
2452 } else if (p_generate) {
2453 return ResourceUID::get_singleton()->create_id(); // Just create a new one, we will be notified of save anyway and fetch the right UID at that time, to keep things simple.
2454 } else {
2455 return ResourceUID::INVALID_ID;
2456 }
2457}
2458
2459static void _scan_extensions_dir(EditorFileSystemDirectory *d, HashSet<String> &extensions) {
2460 int fc = d->get_file_count();
2461 for (int i = 0; i < fc; i++) {
2462 if (d->get_file_type(i) == SNAME("GDExtension")) {
2463 extensions.insert(d->get_file_path(i));
2464 }
2465 }
2466 int dc = d->get_subdir_count();
2467 for (int i = 0; i < dc; i++) {
2468 _scan_extensions_dir(d->get_subdir(i), extensions);
2469 }
2470}
2471bool EditorFileSystem::_scan_extensions() {
2472 EditorFileSystemDirectory *d = get_filesystem();
2473 HashSet<String> extensions;
2474
2475 _scan_extensions_dir(d, extensions);
2476
2477 //verify against loaded extensions
2478
2479 Vector<String> extensions_added;
2480 Vector<String> extensions_removed;
2481
2482 for (const String &E : extensions) {
2483 if (!GDExtensionManager::get_singleton()->is_extension_loaded(E)) {
2484 extensions_added.push_back(E);
2485 }
2486 }
2487
2488 Vector<String> loaded_extensions = GDExtensionManager::get_singleton()->get_loaded_extensions();
2489 for (int i = 0; i < loaded_extensions.size(); i++) {
2490 if (!extensions.has(loaded_extensions[i])) {
2491 extensions_removed.push_back(loaded_extensions[i]);
2492 }
2493 }
2494
2495 String extension_list_config_file = GDExtension::get_extension_list_config_file();
2496 if (extensions.size()) {
2497 if (extensions_added.size() || extensions_removed.size()) { //extensions were added or removed
2498 Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);
2499 for (const String &E : extensions) {
2500 f->store_line(E);
2501 }
2502 }
2503 } else {
2504 if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) { //extensions were removed
2505 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
2506 da->remove(extension_list_config_file);
2507 }
2508 }
2509
2510 bool needs_restart = false;
2511 for (int i = 0; i < extensions_added.size(); i++) {
2512 GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extensions_added[i]);
2513 if (st == GDExtensionManager::LOAD_STATUS_FAILED) {
2514 EditorNode::get_singleton()->add_io_error("Error loading extension: " + extensions_added[i]);
2515 } else if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
2516 needs_restart = true;
2517 }
2518 }
2519 for (int i = 0; i < extensions_removed.size(); i++) {
2520 GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extensions_removed[i]);
2521 if (st == GDExtensionManager::LOAD_STATUS_FAILED) {
2522 EditorNode::get_singleton()->add_io_error("Error removing extension: " + extensions_added[i]);
2523 } else if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
2524 needs_restart = true;
2525 }
2526 }
2527
2528 return needs_restart;
2529}
2530
2531void EditorFileSystem::_bind_methods() {
2532 ClassDB::bind_method(D_METHOD("get_filesystem"), &EditorFileSystem::get_filesystem);
2533 ClassDB::bind_method(D_METHOD("is_scanning"), &EditorFileSystem::is_scanning);
2534 ClassDB::bind_method(D_METHOD("get_scanning_progress"), &EditorFileSystem::get_scanning_progress);
2535 ClassDB::bind_method(D_METHOD("scan"), &EditorFileSystem::scan);
2536 ClassDB::bind_method(D_METHOD("scan_sources"), &EditorFileSystem::scan_changes);
2537 ClassDB::bind_method(D_METHOD("update_file", "path"), &EditorFileSystem::update_file);
2538 ClassDB::bind_method(D_METHOD("get_filesystem_path", "path"), &EditorFileSystem::get_filesystem_path);
2539 ClassDB::bind_method(D_METHOD("get_file_type", "path"), &EditorFileSystem::get_file_type);
2540 ClassDB::bind_method(D_METHOD("reimport_files", "files"), &EditorFileSystem::reimport_files);
2541
2542 ADD_SIGNAL(MethodInfo("filesystem_changed"));
2543 ADD_SIGNAL(MethodInfo("script_classes_updated"));
2544 ADD_SIGNAL(MethodInfo("sources_changed", PropertyInfo(Variant::BOOL, "exist")));
2545 ADD_SIGNAL(MethodInfo("resources_reimported", PropertyInfo(Variant::PACKED_STRING_ARRAY, "resources")));
2546 ADD_SIGNAL(MethodInfo("resources_reload", PropertyInfo(Variant::PACKED_STRING_ARRAY, "resources")));
2547}
2548
2549void EditorFileSystem::_update_extensions() {
2550 valid_extensions.clear();
2551 import_extensions.clear();
2552 textfile_extensions.clear();
2553
2554 List<String> extensionsl;
2555 ResourceLoader::get_recognized_extensions_for_type("", &extensionsl);
2556 for (const String &E : extensionsl) {
2557 valid_extensions.insert(E);
2558 }
2559
2560 const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false);
2561 for (const String &E : textfile_ext) {
2562 if (valid_extensions.has(E)) {
2563 continue;
2564 }
2565 valid_extensions.insert(E);
2566 textfile_extensions.insert(E);
2567 }
2568
2569 extensionsl.clear();
2570 ResourceFormatImporter::get_singleton()->get_recognized_extensions(&extensionsl);
2571 for (const String &E : extensionsl) {
2572 import_extensions.insert(E);
2573 }
2574}
2575
2576void EditorFileSystem::add_import_format_support_query(Ref<EditorFileSystemImportFormatSupportQuery> p_query) {
2577 ERR_FAIL_COND(import_support_queries.find(p_query) != -1);
2578 import_support_queries.push_back(p_query);
2579}
2580void EditorFileSystem::remove_import_format_support_query(Ref<EditorFileSystemImportFormatSupportQuery> p_query) {
2581 import_support_queries.erase(p_query);
2582}
2583
2584EditorFileSystem::EditorFileSystem() {
2585 ResourceLoader::import = _resource_import;
2586 reimport_on_missing_imported_files = GLOBAL_GET("editor/import/reimport_missing_imported_files");
2587 singleton = this;
2588 filesystem = memnew(EditorFileSystemDirectory); //like, empty
2589 filesystem->parent = nullptr;
2590
2591 new_filesystem = nullptr;
2592
2593 // This should probably also work on Unix and use the string it returns for FAT32 or exFAT
2594 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
2595 using_fat32_or_exfat = (da->get_filesystem_type() == "FAT32" || da->get_filesystem_type() == "exFAT");
2596
2597 scan_total = 0;
2598 MessageQueue::get_singleton()->push_callable(callable_mp(ResourceUID::get_singleton(), &ResourceUID::clear)); // Will be updated on scan.
2599 ResourceSaver::set_get_resource_id_for_path(_resource_saver_get_resource_id_for_path);
2600}
2601
2602EditorFileSystem::~EditorFileSystem() {
2603 ResourceSaver::set_get_resource_id_for_path(nullptr);
2604}
2605