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 | |
48 | EditorFileSystem *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 | |
52 | void EditorFileSystemDirectory::sort_files() { |
53 | files.sort_custom<FileInfoSort>(); |
54 | } |
55 | |
56 | int 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 | |
65 | int 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 | |
75 | void 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 | |
80 | int EditorFileSystemDirectory::get_subdir_count() const { |
81 | return subdirs.size(); |
82 | } |
83 | |
84 | EditorFileSystemDirectory *EditorFileSystemDirectory::get_subdir(int p_idx) { |
85 | ERR_FAIL_INDEX_V(p_idx, subdirs.size(), nullptr); |
86 | return subdirs[p_idx]; |
87 | } |
88 | |
89 | int EditorFileSystemDirectory::get_file_count() const { |
90 | return files.size(); |
91 | } |
92 | |
93 | String 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 | |
99 | String 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 | |
110 | String 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 | |
121 | Vector<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 | |
145 | bool 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 | |
150 | uint64_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 | |
155 | String EditorFileSystemDirectory::get_file_script_class_name(int p_idx) const { |
156 | return files[p_idx]->script_class_name; |
157 | } |
158 | |
159 | String EditorFileSystemDirectory::get_file_script_class_extends(int p_idx) const { |
160 | return files[p_idx]->script_class_extends; |
161 | } |
162 | |
163 | String EditorFileSystemDirectory::get_file_script_class_icon_path(int p_idx) const { |
164 | return files[p_idx]->script_class_icon_path; |
165 | } |
166 | |
167 | StringName 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 | |
172 | StringName 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 | |
177 | String EditorFileSystemDirectory::get_name() { |
178 | return name; |
179 | } |
180 | |
181 | EditorFileSystemDirectory *EditorFileSystemDirectory::get_parent() { |
182 | return parent; |
183 | } |
184 | |
185 | void 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 | |
202 | EditorFileSystemDirectory::EditorFileSystemDirectory() { |
203 | modified_time = 0; |
204 | parent = nullptr; |
205 | } |
206 | |
207 | EditorFileSystemDirectory::~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 | |
217 | void 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 | |
342 | void 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 | |
354 | void EditorFileSystem::_thread_func(void *_userdata) { |
355 | EditorFileSystem *sd = (EditorFileSystem *)_userdata; |
356 | sd->_scan_filesystem(); |
357 | } |
358 | |
359 | bool 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 | |
525 | bool 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 | |
564 | bool 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 | |
710 | void 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 | |
750 | void 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 | |
756 | EditorFileSystem::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 | |
764 | void 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 | |
960 | void 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 | |
1149 | void 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 | |
1161 | void 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 = ≺ |
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 | |
1174 | void 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 = ≺ |
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 | |
1215 | void 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 | |
1299 | bool EditorFileSystem::is_scanning() const { |
1300 | return scanning || scanning_changes; |
1301 | } |
1302 | |
1303 | float EditorFileSystem::get_scanning_progress() const { |
1304 | return scan_total; |
1305 | } |
1306 | |
1307 | EditorFileSystemDirectory *EditorFileSystem::get_filesystem() { |
1308 | return filesystem; |
1309 | } |
1310 | |
1311 | void 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 | |
1343 | bool 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 | |
1421 | String 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 | |
1432 | EditorFileSystemDirectory *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 | |
1450 | EditorFileSystemDirectory *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 | |
1498 | void 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 | |
1508 | Vector<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 | |
1530 | String 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 | |
1548 | void 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 | |
1620 | void 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 | |
1631 | void 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 | |
1637 | void 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 | |
1737 | HashSet<String> EditorFileSystem::get_valid_extensions() const { |
1738 | return valid_extensions; |
1739 | } |
1740 | |
1741 | Error 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 ¶m : 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 | |
1933 | Error 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 | |
2181 | void 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 | |
2198 | void 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 | |
2207 | void 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 | |
2212 | void 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 | |
2343 | Error 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 | |
2348 | Error 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 | |
2358 | bool 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 | |
2378 | bool EditorFileSystem::is_group_file(const String &p_path) const { |
2379 | return group_file_cache.has(p_path); |
2380 | } |
2381 | |
2382 | void 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 ¶m : 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 | |
2419 | void 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 | |
2429 | ResourceUID::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 | |
2459 | static 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 | } |
2471 | bool 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 | |
2531 | void 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 | |
2549 | void 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 | |
2576 | void 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 | } |
2580 | void EditorFileSystem::remove_import_format_support_query(Ref<EditorFileSystemImportFormatSupportQuery> p_query) { |
2581 | import_support_queries.erase(p_query); |
2582 | } |
2583 | |
2584 | EditorFileSystem::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 | |
2602 | EditorFileSystem::~EditorFileSystem() { |
2603 | ResourceSaver::set_get_resource_id_for_path(nullptr); |
2604 | } |
2605 | |