1 | /**************************************************************************/ |
2 | /* resource_saver.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_saver.h" |
32 | #include "core/config/project_settings.h" |
33 | #include "core/io/file_access.h" |
34 | #include "core/io/resource_loader.h" |
35 | #include "core/object/script_language.h" |
36 | |
37 | Ref<ResourceFormatSaver> ResourceSaver::saver[MAX_SAVERS]; |
38 | |
39 | int ResourceSaver::saver_count = 0; |
40 | bool ResourceSaver::timestamp_on_save = false; |
41 | ResourceSavedCallback ResourceSaver::save_callback = nullptr; |
42 | ResourceSaverGetResourceIDForPath ResourceSaver::save_get_id_for_path = nullptr; |
43 | |
44 | Error ResourceFormatSaver::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) { |
45 | Error err = ERR_METHOD_NOT_FOUND; |
46 | GDVIRTUAL_CALL(_save, p_resource, p_path, p_flags, err); |
47 | return err; |
48 | } |
49 | |
50 | Error ResourceFormatSaver::set_uid(const String &p_path, ResourceUID::ID p_uid) { |
51 | Error err = ERR_FILE_UNRECOGNIZED; |
52 | GDVIRTUAL_CALL(_set_uid, p_path, p_uid, err); |
53 | return err; |
54 | } |
55 | |
56 | bool ResourceFormatSaver::recognize(const Ref<Resource> &p_resource) const { |
57 | bool success = false; |
58 | GDVIRTUAL_CALL(_recognize, p_resource, success); |
59 | return success; |
60 | } |
61 | |
62 | void ResourceFormatSaver::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const { |
63 | PackedStringArray exts; |
64 | if (GDVIRTUAL_CALL(_get_recognized_extensions, p_resource, exts)) { |
65 | const String *r = exts.ptr(); |
66 | for (int i = 0; i < exts.size(); ++i) { |
67 | p_extensions->push_back(r[i]); |
68 | } |
69 | } |
70 | } |
71 | |
72 | bool ResourceFormatSaver::recognize_path(const Ref<Resource> &p_resource, const String &p_path) const { |
73 | bool ret = false; |
74 | if (GDVIRTUAL_CALL(_recognize_path, p_resource, p_path, ret)) { |
75 | return ret; |
76 | } |
77 | |
78 | String extension = p_path.get_extension(); |
79 | |
80 | List<String> extensions; |
81 | get_recognized_extensions(p_resource, &extensions); |
82 | |
83 | for (const String &E : extensions) { |
84 | if (E.nocasecmp_to(extension) == 0) { |
85 | return true; |
86 | } |
87 | } |
88 | |
89 | return false; |
90 | } |
91 | |
92 | void ResourceFormatSaver::_bind_methods() { |
93 | GDVIRTUAL_BIND(_save, "resource" , "path" , "flags" ); |
94 | GDVIRTUAL_BIND(_set_uid, "path" , "uid" ); |
95 | GDVIRTUAL_BIND(_recognize, "resource" ); |
96 | GDVIRTUAL_BIND(_get_recognized_extensions, "resource" ); |
97 | GDVIRTUAL_BIND(_recognize_path, "resource" , "path" ); |
98 | } |
99 | |
100 | Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) { |
101 | String path = p_path; |
102 | if (path.is_empty()) { |
103 | path = p_resource->get_path(); |
104 | } |
105 | ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Can't save resource to empty path. Provide non-empty path or a Resource with non-empty resource_path." ); |
106 | |
107 | String extension = path.get_extension(); |
108 | Error err = ERR_FILE_UNRECOGNIZED; |
109 | |
110 | for (int i = 0; i < saver_count; i++) { |
111 | if (!saver[i]->recognize(p_resource)) { |
112 | continue; |
113 | } |
114 | |
115 | if (!saver[i]->recognize_path(p_resource, path)) { |
116 | continue; |
117 | } |
118 | |
119 | String old_path = p_resource->get_path(); |
120 | |
121 | String local_path = ProjectSettings::get_singleton()->localize_path(path); |
122 | |
123 | Ref<Resource> rwcopy = p_resource; |
124 | if (p_flags & FLAG_CHANGE_PATH) { |
125 | rwcopy->set_path(local_path); |
126 | } |
127 | |
128 | err = saver[i]->save(p_resource, path, p_flags); |
129 | |
130 | if (err == OK) { |
131 | #ifdef TOOLS_ENABLED |
132 | |
133 | ((Resource *)p_resource.ptr())->set_edited(false); |
134 | if (timestamp_on_save) { |
135 | uint64_t mt = FileAccess::get_modified_time(path); |
136 | |
137 | ((Resource *)p_resource.ptr())->set_last_modified_time(mt); |
138 | } |
139 | #endif |
140 | |
141 | if (p_flags & FLAG_CHANGE_PATH) { |
142 | rwcopy->set_path(old_path); |
143 | } |
144 | |
145 | if (save_callback && path.begins_with("res://" )) { |
146 | save_callback(p_resource, path); |
147 | } |
148 | |
149 | return OK; |
150 | } |
151 | } |
152 | |
153 | return err; |
154 | } |
155 | |
156 | Error ResourceSaver::set_uid(const String &p_path, ResourceUID::ID p_uid) { |
157 | String path = p_path; |
158 | |
159 | ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Can't update UID to empty path. Provide non-empty path." ); |
160 | |
161 | Error err = ERR_FILE_UNRECOGNIZED; |
162 | |
163 | for (int i = 0; i < saver_count; i++) { |
164 | err = saver[i]->set_uid(path, p_uid); |
165 | if (err == OK) { |
166 | break; |
167 | } |
168 | } |
169 | |
170 | return err; |
171 | } |
172 | |
173 | void ResourceSaver::set_save_callback(ResourceSavedCallback p_callback) { |
174 | save_callback = p_callback; |
175 | } |
176 | |
177 | void ResourceSaver::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) { |
178 | for (int i = 0; i < saver_count; i++) { |
179 | saver[i]->get_recognized_extensions(p_resource, p_extensions); |
180 | } |
181 | } |
182 | |
183 | void ResourceSaver::add_resource_format_saver(Ref<ResourceFormatSaver> p_format_saver, bool p_at_front) { |
184 | ERR_FAIL_COND_MSG(p_format_saver.is_null(), "It's not a reference to a valid ResourceFormatSaver object." ); |
185 | ERR_FAIL_COND(saver_count >= MAX_SAVERS); |
186 | |
187 | if (p_at_front) { |
188 | for (int i = saver_count; i > 0; i--) { |
189 | saver[i] = saver[i - 1]; |
190 | } |
191 | saver[0] = p_format_saver; |
192 | saver_count++; |
193 | } else { |
194 | saver[saver_count++] = p_format_saver; |
195 | } |
196 | } |
197 | |
198 | void ResourceSaver::remove_resource_format_saver(Ref<ResourceFormatSaver> p_format_saver) { |
199 | ERR_FAIL_COND_MSG(p_format_saver.is_null(), "It's not a reference to a valid ResourceFormatSaver object." ); |
200 | |
201 | // Find saver |
202 | int i = 0; |
203 | for (; i < saver_count; ++i) { |
204 | if (saver[i] == p_format_saver) { |
205 | break; |
206 | } |
207 | } |
208 | |
209 | ERR_FAIL_COND(i >= saver_count); // Not found |
210 | |
211 | // Shift next savers up |
212 | for (; i < saver_count - 1; ++i) { |
213 | saver[i] = saver[i + 1]; |
214 | } |
215 | saver[saver_count - 1].unref(); |
216 | --saver_count; |
217 | } |
218 | |
219 | Ref<ResourceFormatSaver> ResourceSaver::_find_custom_resource_format_saver(String path) { |
220 | for (int i = 0; i < saver_count; ++i) { |
221 | if (saver[i]->get_script_instance() && saver[i]->get_script_instance()->get_script()->get_path() == path) { |
222 | return saver[i]; |
223 | } |
224 | } |
225 | return Ref<ResourceFormatSaver>(); |
226 | } |
227 | |
228 | bool ResourceSaver::add_custom_resource_format_saver(String script_path) { |
229 | if (_find_custom_resource_format_saver(script_path).is_valid()) { |
230 | return false; |
231 | } |
232 | |
233 | Ref<Resource> res = ResourceLoader::load(script_path); |
234 | ERR_FAIL_COND_V(res.is_null(), false); |
235 | ERR_FAIL_COND_V(!res->is_class("Script" ), false); |
236 | |
237 | Ref<Script> s = res; |
238 | StringName ibt = s->get_instance_base_type(); |
239 | bool valid_type = ClassDB::is_parent_class(ibt, "ResourceFormatSaver" ); |
240 | ERR_FAIL_COND_V_MSG(!valid_type, false, "Script does not inherit a CustomResourceSaver: " + script_path + "." ); |
241 | |
242 | Object *obj = ClassDB::instantiate(ibt); |
243 | |
244 | ERR_FAIL_NULL_V_MSG(obj, false, "Cannot instance script as custom resource saver, expected 'ResourceFormatSaver' inheritance, got: " + String(ibt) + "." ); |
245 | |
246 | Ref<ResourceFormatSaver> crl = Object::cast_to<ResourceFormatSaver>(obj); |
247 | crl->set_script(s); |
248 | ResourceSaver::add_resource_format_saver(crl); |
249 | |
250 | return true; |
251 | } |
252 | |
253 | void ResourceSaver::add_custom_savers() { |
254 | // Custom resource savers exploits global class names |
255 | |
256 | String custom_saver_base_class = ResourceFormatSaver::get_class_static(); |
257 | |
258 | List<StringName> global_classes; |
259 | ScriptServer::get_global_class_list(&global_classes); |
260 | |
261 | for (const StringName &class_name : global_classes) { |
262 | StringName base_class = ScriptServer::get_global_class_native_base(class_name); |
263 | |
264 | if (base_class == custom_saver_base_class) { |
265 | String path = ScriptServer::get_global_class_path(class_name); |
266 | add_custom_resource_format_saver(path); |
267 | } |
268 | } |
269 | } |
270 | |
271 | void ResourceSaver::remove_custom_savers() { |
272 | Vector<Ref<ResourceFormatSaver>> custom_savers; |
273 | for (int i = 0; i < saver_count; ++i) { |
274 | if (saver[i]->get_script_instance()) { |
275 | custom_savers.push_back(saver[i]); |
276 | } |
277 | } |
278 | |
279 | for (int i = 0; i < custom_savers.size(); ++i) { |
280 | remove_resource_format_saver(custom_savers[i]); |
281 | } |
282 | } |
283 | |
284 | ResourceUID::ID ResourceSaver::get_resource_id_for_path(const String &p_path, bool p_generate) { |
285 | if (save_get_id_for_path) { |
286 | return save_get_id_for_path(p_path, p_generate); |
287 | } |
288 | return ResourceUID::INVALID_ID; |
289 | } |
290 | |
291 | void ResourceSaver::set_get_resource_id_for_path(ResourceSaverGetResourceIDForPath p_callback) { |
292 | save_get_id_for_path = p_callback; |
293 | } |
294 | |