| 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 | |