1/**************************************************************************/
2/* resource_importer.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 "resource_importer.h"
32
33#include "core/config/project_settings.h"
34#include "core/io/config_file.h"
35#include "core/os/os.h"
36#include "core/variant/variant_parser.h"
37
38bool ResourceFormatImporter::SortImporterByName::operator()(const Ref<ResourceImporter> &p_a, const Ref<ResourceImporter> &p_b) const {
39 return p_a->get_importer_name() < p_b->get_importer_name();
40}
41
42Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndType &r_path_and_type, bool *r_valid) const {
43 Error err;
44 Ref<FileAccess> f = FileAccess::open(p_path + ".import", FileAccess::READ, &err);
45
46 if (f.is_null()) {
47 if (r_valid) {
48 *r_valid = false;
49 }
50 return err;
51 }
52
53 VariantParser::StreamFile stream;
54 stream.f = f;
55
56 String assign;
57 Variant value;
58 VariantParser::Tag next_tag;
59
60 if (r_valid) {
61 *r_valid = true;
62 }
63
64 int lines = 0;
65 String error_text;
66 bool path_found = false; //first match must have priority
67 while (true) {
68 assign = Variant();
69 next_tag.fields.clear();
70 next_tag.name = String();
71
72 err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true);
73 if (err == ERR_FILE_EOF) {
74 return OK;
75 } else if (err != OK) {
76 ERR_PRINT("ResourceFormatImporter::load - " + p_path + ".import:" + itos(lines) + " error: " + error_text);
77 return err;
78 }
79
80 if (!assign.is_empty()) {
81 if (!path_found && assign.begins_with("path.") && r_path_and_type.path.is_empty()) {
82 String feature = assign.get_slicec('.', 1);
83 if (OS::get_singleton()->has_feature(feature)) {
84 r_path_and_type.path = value;
85 path_found = true; //first match must have priority
86 }
87
88 } else if (!path_found && assign == "path") {
89 r_path_and_type.path = value;
90 path_found = true; //first match must have priority
91 } else if (assign == "type") {
92 r_path_and_type.type = ClassDB::get_compatibility_remapped_class(value);
93 } else if (assign == "importer") {
94 r_path_and_type.importer = value;
95 } else if (assign == "uid") {
96 r_path_and_type.uid = ResourceUID::get_singleton()->text_to_id(value);
97 } else if (assign == "group_file") {
98 r_path_and_type.group_file = value;
99 } else if (assign == "metadata") {
100 r_path_and_type.metadata = value;
101 } else if (assign == "valid") {
102 if (r_valid) {
103 *r_valid = value;
104 }
105 }
106
107 } else if (next_tag.name != "remap") {
108 break;
109 }
110 }
111
112#ifdef TOOLS_ENABLED
113 if (r_path_and_type.metadata && !r_path_and_type.path.is_empty()) {
114 Dictionary meta = r_path_and_type.metadata;
115 if (meta.has("has_editor_variant")) {
116 r_path_and_type.path = r_path_and_type.path.get_basename() + ".editor." + r_path_and_type.path.get_extension();
117 }
118 }
119#endif
120
121 if (r_path_and_type.path.is_empty() || r_path_and_type.type.is_empty()) {
122 return ERR_FILE_CORRUPT;
123 }
124 return OK;
125}
126
127Ref<Resource> ResourceFormatImporter::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
128 PathAndType pat;
129 Error err = _get_path_and_type(p_path, pat);
130
131 if (err != OK) {
132 if (r_error) {
133 *r_error = err;
134 }
135
136 return Ref<Resource>();
137 }
138
139 Ref<Resource> res = ResourceLoader::_load(pat.path, p_path, pat.type, p_cache_mode, r_error, p_use_sub_threads, r_progress);
140
141#ifdef TOOLS_ENABLED
142 if (res.is_valid()) {
143 res->set_import_last_modified_time(res->get_last_modified_time()); //pass this, if used
144 res->set_import_path(pat.path);
145 }
146#endif
147
148 return res;
149}
150
151void ResourceFormatImporter::get_recognized_extensions(List<String> *p_extensions) const {
152 HashSet<String> found;
153
154 for (int i = 0; i < importers.size(); i++) {
155 List<String> local_exts;
156 importers[i]->get_recognized_extensions(&local_exts);
157 for (const String &F : local_exts) {
158 if (!found.has(F)) {
159 p_extensions->push_back(F);
160 found.insert(F);
161 }
162 }
163 }
164}
165
166void ResourceFormatImporter::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
167 if (p_type.is_empty()) {
168 get_recognized_extensions(p_extensions);
169 return;
170 }
171
172 HashSet<String> found;
173
174 for (int i = 0; i < importers.size(); i++) {
175 String res_type = importers[i]->get_resource_type();
176 if (res_type.is_empty()) {
177 continue;
178 }
179
180 if (!ClassDB::is_parent_class(res_type, p_type)) {
181 continue;
182 }
183
184 List<String> local_exts;
185 importers[i]->get_recognized_extensions(&local_exts);
186 for (const String &F : local_exts) {
187 if (!found.has(F)) {
188 p_extensions->push_back(F);
189 found.insert(F);
190 }
191 }
192 }
193}
194
195bool ResourceFormatImporter::exists(const String &p_path) const {
196 return FileAccess::exists(p_path + ".import");
197}
198
199bool ResourceFormatImporter::recognize_path(const String &p_path, const String &p_for_type) const {
200 return FileAccess::exists(p_path + ".import");
201}
202
203Error ResourceFormatImporter::get_import_order_threads_and_importer(const String &p_path, int &r_order, bool &r_can_threads, String &r_importer) const {
204 r_order = 0;
205 r_importer = "";
206
207 r_can_threads = false;
208 Ref<ResourceImporter> importer;
209
210 if (FileAccess::exists(p_path + ".import")) {
211 PathAndType pat;
212 Error err = _get_path_and_type(p_path, pat);
213
214 if (err == OK) {
215 importer = get_importer_by_name(pat.importer);
216 }
217 } else {
218 importer = get_importer_by_extension(p_path.get_extension().to_lower());
219 }
220
221 if (importer.is_valid()) {
222 r_order = importer->get_import_order();
223 r_importer = importer->get_importer_name();
224 r_can_threads = importer->can_import_threaded();
225 return OK;
226 } else {
227 return ERR_INVALID_PARAMETER;
228 }
229}
230
231int ResourceFormatImporter::get_import_order(const String &p_path) const {
232 Ref<ResourceImporter> importer;
233
234 if (FileAccess::exists(p_path + ".import")) {
235 PathAndType pat;
236 Error err = _get_path_and_type(p_path, pat);
237
238 if (err == OK) {
239 importer = get_importer_by_name(pat.importer);
240 }
241 } else {
242 importer = get_importer_by_extension(p_path.get_extension().to_lower());
243 }
244
245 if (importer.is_valid()) {
246 return importer->get_import_order();
247 }
248
249 return 0;
250}
251
252bool ResourceFormatImporter::handles_type(const String &p_type) const {
253 for (int i = 0; i < importers.size(); i++) {
254 String res_type = importers[i]->get_resource_type();
255 if (res_type.is_empty()) {
256 continue;
257 }
258 if (ClassDB::is_parent_class(res_type, p_type)) {
259 return true;
260 }
261 }
262
263 return true;
264}
265
266String ResourceFormatImporter::get_internal_resource_path(const String &p_path) const {
267 PathAndType pat;
268 Error err = _get_path_and_type(p_path, pat);
269
270 if (err != OK) {
271 return String();
272 }
273
274 return pat.path;
275}
276
277void ResourceFormatImporter::get_internal_resource_path_list(const String &p_path, List<String> *r_paths) {
278 Error err;
279 Ref<FileAccess> f = FileAccess::open(p_path + ".import", FileAccess::READ, &err);
280
281 if (f.is_null()) {
282 return;
283 }
284
285 VariantParser::StreamFile stream;
286 stream.f = f;
287
288 String assign;
289 Variant value;
290 VariantParser::Tag next_tag;
291
292 int lines = 0;
293 String error_text;
294 while (true) {
295 assign = Variant();
296 next_tag.fields.clear();
297 next_tag.name = String();
298
299 err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true);
300 if (err == ERR_FILE_EOF) {
301 return;
302 } else if (err != OK) {
303 ERR_PRINT("ResourceFormatImporter::get_internal_resource_path_list - " + p_path + ".import:" + itos(lines) + " error: " + error_text);
304 return;
305 }
306
307 if (!assign.is_empty()) {
308 if (assign.begins_with("path.")) {
309 r_paths->push_back(value);
310 } else if (assign == "path") {
311 r_paths->push_back(value);
312 }
313 } else if (next_tag.name != "remap") {
314 break;
315 }
316 }
317}
318
319String ResourceFormatImporter::get_import_group_file(const String &p_path) const {
320 bool valid = true;
321 PathAndType pat;
322 _get_path_and_type(p_path, pat, &valid);
323 return valid ? pat.group_file : String();
324}
325
326bool ResourceFormatImporter::is_import_valid(const String &p_path) const {
327 bool valid = true;
328 PathAndType pat;
329 _get_path_and_type(p_path, pat, &valid);
330 return valid;
331}
332
333String ResourceFormatImporter::get_resource_type(const String &p_path) const {
334 PathAndType pat;
335 Error err = _get_path_and_type(p_path, pat);
336
337 if (err != OK) {
338 return "";
339 }
340
341 return pat.type;
342}
343
344ResourceUID::ID ResourceFormatImporter::get_resource_uid(const String &p_path) const {
345 PathAndType pat;
346 Error err = _get_path_and_type(p_path, pat);
347
348 if (err != OK) {
349 return ResourceUID::INVALID_ID;
350 }
351
352 return pat.uid;
353}
354
355Variant ResourceFormatImporter::get_resource_metadata(const String &p_path) const {
356 PathAndType pat;
357 Error err = _get_path_and_type(p_path, pat);
358
359 if (err != OK) {
360 return Variant();
361 }
362
363 return pat.metadata;
364}
365void ResourceFormatImporter::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
366 PathAndType pat;
367 Error err = _get_path_and_type(p_path, pat);
368
369 if (err != OK) {
370 return;
371 }
372
373 ResourceLoader::get_classes_used(pat.path, r_classes);
374}
375
376void ResourceFormatImporter::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
377 PathAndType pat;
378 Error err = _get_path_and_type(p_path, pat);
379
380 if (err != OK) {
381 return;
382 }
383
384 ResourceLoader::get_dependencies(pat.path, p_dependencies, p_add_types);
385}
386
387Ref<ResourceImporter> ResourceFormatImporter::get_importer_by_name(const String &p_name) const {
388 for (int i = 0; i < importers.size(); i++) {
389 if (importers[i]->get_importer_name() == p_name) {
390 return importers[i];
391 }
392 }
393
394 return Ref<ResourceImporter>();
395}
396
397void ResourceFormatImporter::add_importer(const Ref<ResourceImporter> &p_importer, bool p_first_priority) {
398 ERR_FAIL_COND(p_importer.is_null());
399 if (p_first_priority) {
400 importers.insert(0, p_importer);
401 } else {
402 importers.push_back(p_importer);
403 }
404}
405
406void ResourceFormatImporter::get_importers_for_extension(const String &p_extension, List<Ref<ResourceImporter>> *r_importers) {
407 for (int i = 0; i < importers.size(); i++) {
408 List<String> local_exts;
409 importers[i]->get_recognized_extensions(&local_exts);
410 for (const String &F : local_exts) {
411 if (p_extension.to_lower() == F) {
412 r_importers->push_back(importers[i]);
413 }
414 }
415 }
416}
417
418void ResourceFormatImporter::get_importers(List<Ref<ResourceImporter>> *r_importers) {
419 for (int i = 0; i < importers.size(); i++) {
420 r_importers->push_back(importers[i]);
421 }
422}
423
424Ref<ResourceImporter> ResourceFormatImporter::get_importer_by_extension(const String &p_extension) const {
425 Ref<ResourceImporter> importer;
426 float priority = 0;
427
428 for (int i = 0; i < importers.size(); i++) {
429 List<String> local_exts;
430 importers[i]->get_recognized_extensions(&local_exts);
431 for (const String &F : local_exts) {
432 if (p_extension.to_lower() == F && importers[i]->get_priority() > priority) {
433 importer = importers[i];
434 priority = importers[i]->get_priority();
435 }
436 }
437 }
438
439 return importer;
440}
441
442String ResourceFormatImporter::get_import_base_path(const String &p_for_file) const {
443 return ProjectSettings::get_singleton()->get_imported_files_path().path_join(p_for_file.get_file() + "-" + p_for_file.md5_text());
444}
445
446bool ResourceFormatImporter::are_import_settings_valid(const String &p_path) const {
447 bool valid = true;
448 PathAndType pat;
449 _get_path_and_type(p_path, pat, &valid);
450
451 if (!valid) {
452 return false;
453 }
454
455 for (int i = 0; i < importers.size(); i++) {
456 if (importers[i]->get_importer_name() == pat.importer) {
457 if (!importers[i]->are_import_settings_valid(p_path)) { //importer thinks this is not valid
458 return false;
459 }
460 }
461 }
462
463 return true;
464}
465
466String ResourceFormatImporter::get_import_settings_hash() const {
467 Vector<Ref<ResourceImporter>> sorted_importers = importers;
468
469 sorted_importers.sort_custom<SortImporterByName>();
470
471 String hash;
472 for (int i = 0; i < sorted_importers.size(); i++) {
473 hash += ":" + sorted_importers[i]->get_importer_name() + ":" + sorted_importers[i]->get_import_settings_string();
474 }
475 return hash.md5_text();
476}
477
478ResourceFormatImporter *ResourceFormatImporter::singleton = nullptr;
479
480ResourceFormatImporter::ResourceFormatImporter() {
481 singleton = this;
482}
483
484//////////////
485
486void ResourceImporter::_bind_methods() {
487 BIND_ENUM_CONSTANT(IMPORT_ORDER_DEFAULT);
488 BIND_ENUM_CONSTANT(IMPORT_ORDER_SCENE);
489}
490
491/////
492
493Error ResourceFormatImporterSaver::set_uid(const String &p_path, ResourceUID::ID p_uid) {
494 Ref<ConfigFile> cf;
495 cf.instantiate();
496 Error err = cf->load(p_path + ".import");
497 if (err != OK) {
498 return err;
499 }
500 cf->set_value("remap", "uid", ResourceUID::get_singleton()->id_to_text(p_uid));
501 cf->save(p_path + ".import");
502
503 return OK;
504}
505