1 /*************************************************************************/
2 /*  export_template_manager.cpp                                          */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
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 "export_template_manager.h"
32 
33 #include "core/io/json.h"
34 #include "core/io/zip_io.h"
35 #include "core/os/dir_access.h"
36 #include "core/os/input.h"
37 #include "core/os/keyboard.h"
38 #include "core/version.h"
39 #include "editor_node.h"
40 #include "editor_scale.h"
41 #include "progress_dialog.h"
42 #include "scene/gui/link_button.h"
43 
_update_template_list()44 void ExportTemplateManager::_update_template_list() {
45 
46 	while (current_hb->get_child_count()) {
47 		memdelete(current_hb->get_child(0));
48 	}
49 
50 	while (installed_vb->get_child_count()) {
51 		memdelete(installed_vb->get_child(0));
52 	}
53 
54 	DirAccess *d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
55 	Error err = d->change_dir(EditorSettings::get_singleton()->get_templates_dir());
56 
57 	Set<String> templates;
58 	d->list_dir_begin();
59 	if (err == OK) {
60 
61 		String c = d->get_next();
62 		while (c != String()) {
63 			if (d->current_is_dir() && !c.begins_with(".")) {
64 				templates.insert(c);
65 			}
66 			c = d->get_next();
67 		}
68 	}
69 	d->list_dir_end();
70 
71 	memdelete(d);
72 
73 	String current_version = VERSION_FULL_CONFIG;
74 	// Downloadable export templates are only available for stable and official alpha/beta/RC builds
75 	// (which always have a number following their status, e.g. "alpha1").
76 	// Therefore, don't display download-related features when using a development version
77 	// (whose builds aren't numbered).
78 	const bool downloads_available =
79 			String(VERSION_STATUS) != String("dev") &&
80 			String(VERSION_STATUS) != String("alpha") &&
81 			String(VERSION_STATUS) != String("beta") &&
82 			String(VERSION_STATUS) != String("rc");
83 
84 	Label *current = memnew(Label);
85 	current->set_h_size_flags(SIZE_EXPAND_FILL);
86 	current_hb->add_child(current);
87 
88 	if (templates.has(current_version)) {
89 		current->add_color_override("font_color", get_color("success_color", "Editor"));
90 
91 		// Only display a redownload button if it can be downloaded in the first place
92 		if (downloads_available) {
93 			Button *redownload = memnew(Button);
94 			redownload->set_text(TTR("Redownload"));
95 			current_hb->add_child(redownload);
96 			redownload->connect("pressed", this, "_download_template", varray(current_version));
97 		}
98 
99 		Button *uninstall = memnew(Button);
100 		uninstall->set_text(TTR("Uninstall"));
101 		current_hb->add_child(uninstall);
102 		current->set_text(current_version + " " + TTR("(Installed)"));
103 		uninstall->connect("pressed", this, "_uninstall_template", varray(current_version));
104 
105 	} else {
106 		current->add_color_override("font_color", get_color("error_color", "Editor"));
107 		Button *redownload = memnew(Button);
108 		redownload->set_text(TTR("Download"));
109 
110 		if (!downloads_available) {
111 			redownload->set_disabled(true);
112 			redownload->set_tooltip(TTR("Official export templates aren't available for development builds."));
113 		}
114 
115 		redownload->connect("pressed", this, "_download_template", varray(current_version));
116 		current_hb->add_child(redownload);
117 		current->set_text(current_version + " " + TTR("(Missing)"));
118 	}
119 
120 	for (Set<String>::Element *E = templates.back(); E; E = E->prev()) {
121 
122 		HBoxContainer *hbc = memnew(HBoxContainer);
123 		Label *version = memnew(Label);
124 		version->set_modulate(get_color("disabled_font_color", "Editor"));
125 		String text = E->get();
126 		if (text == current_version) {
127 			text += " " + TTR("(Current)");
128 		}
129 		version->set_text(text);
130 		version->set_h_size_flags(SIZE_EXPAND_FILL);
131 		hbc->add_child(version);
132 
133 		Button *uninstall = memnew(Button);
134 
135 		uninstall->set_text(TTR("Uninstall"));
136 		hbc->add_child(uninstall);
137 		uninstall->connect("pressed", this, "_uninstall_template", varray(E->get()));
138 
139 		installed_vb->add_child(hbc);
140 	}
141 }
142 
_download_template(const String & p_version)143 void ExportTemplateManager::_download_template(const String &p_version) {
144 
145 	while (template_list->get_child_count()) {
146 		memdelete(template_list->get_child(0));
147 	}
148 	template_downloader->popup_centered_minsize();
149 	template_list_state->set_text(TTR("Retrieving mirrors, please wait..."));
150 	template_download_progress->set_max(100);
151 	template_download_progress->set_value(0);
152 	request_mirror->request("https://godotengine.org/mirrorlist/" + p_version + ".json");
153 	template_list_state->show();
154 	template_download_progress->show();
155 }
156 
_uninstall_template(const String & p_version)157 void ExportTemplateManager::_uninstall_template(const String &p_version) {
158 
159 	remove_confirm->set_text(vformat(TTR("Remove template version '%s'?"), p_version));
160 	remove_confirm->popup_centered_minsize();
161 	to_remove = p_version;
162 }
163 
_uninstall_template_confirm()164 void ExportTemplateManager::_uninstall_template_confirm() {
165 
166 	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
167 	const String &templates_dir = EditorSettings::get_singleton()->get_templates_dir();
168 	Error err = da->change_dir(templates_dir);
169 	ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir + "'.");
170 	err = da->change_dir(to_remove);
171 	ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir.plus_file(to_remove) + "'.");
172 
173 	err = da->erase_contents_recursive();
174 	ERR_FAIL_COND_MSG(err != OK, "Could not remove all templates in '" + templates_dir.plus_file(to_remove) + "'.");
175 
176 	da->change_dir("..");
177 	err = da->remove(to_remove);
178 	ERR_FAIL_COND_MSG(err != OK, "Could not remove templates directory at '" + templates_dir.plus_file(to_remove) + "'.");
179 
180 	_update_template_list();
181 }
182 
_install_from_file(const String & p_file,bool p_use_progress)183 bool ExportTemplateManager::_install_from_file(const String &p_file, bool p_use_progress) {
184 
185 	// unzClose() will take care of closing the file stored in the unzFile,
186 	// so we don't need to `memdelete(fa)` in this method.
187 	FileAccess *fa = NULL;
188 	zlib_filefunc_def io = zipio_create_io_from_file(&fa);
189 
190 	unzFile pkg = unzOpen2(p_file.utf8().get_data(), &io);
191 	if (!pkg) {
192 
193 		EditorNode::get_singleton()->show_warning(TTR("Can't open export templates zip."));
194 		return false;
195 	}
196 	int ret = unzGoToFirstFile(pkg);
197 
198 	int fc = 0; //count them and find version
199 	String version;
200 	String contents_dir;
201 
202 	while (ret == UNZ_OK) {
203 
204 		unz_file_info info;
205 		char fname[16384];
206 		ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0);
207 
208 		String file = fname;
209 
210 		if (file.ends_with("version.txt")) {
211 
212 			Vector<uint8_t> data;
213 			data.resize(info.uncompressed_size);
214 
215 			//read
216 			unzOpenCurrentFile(pkg);
217 			ret = unzReadCurrentFile(pkg, data.ptrw(), data.size());
218 			unzCloseCurrentFile(pkg);
219 
220 			String data_str;
221 			data_str.parse_utf8((const char *)data.ptr(), data.size());
222 			data_str = data_str.strip_edges();
223 
224 			// Version number should be of the form major.minor[.patch].status[.module_config]
225 			// so it can in theory have 3 or more slices.
226 			if (data_str.get_slice_count(".") < 3) {
227 				EditorNode::get_singleton()->show_warning(vformat(TTR("Invalid version.txt format inside templates: %s."), data_str));
228 				unzClose(pkg);
229 				return false;
230 			}
231 
232 			version = data_str;
233 			contents_dir = file.get_base_dir().trim_suffix("/").trim_suffix("\\");
234 		}
235 
236 		if (file.get_file().size() != 0) {
237 			fc++;
238 		}
239 
240 		ret = unzGoToNextFile(pkg);
241 	}
242 
243 	if (version == String()) {
244 		EditorNode::get_singleton()->show_warning(TTR("No version.txt found inside templates."));
245 		unzClose(pkg);
246 		return false;
247 	}
248 
249 	String template_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(version);
250 
251 	DirAccessRef d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
252 	Error err = d->make_dir_recursive(template_path);
253 	if (err != OK) {
254 		EditorNode::get_singleton()->show_warning(TTR("Error creating path for templates:") + "\n" + template_path);
255 		unzClose(pkg);
256 		return false;
257 	}
258 
259 	ret = unzGoToFirstFile(pkg);
260 
261 	EditorProgress *p = NULL;
262 	if (p_use_progress) {
263 		p = memnew(EditorProgress("ltask", TTR("Extracting Export Templates"), fc));
264 	}
265 
266 	fc = 0;
267 
268 	while (ret == UNZ_OK) {
269 
270 		//get filename
271 		unz_file_info info;
272 		char fname[16384];
273 		unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0);
274 
275 		String file_path(String(fname).simplify_path());
276 
277 		String file = file_path.get_file();
278 
279 		if (file.size() == 0) {
280 			ret = unzGoToNextFile(pkg);
281 			continue;
282 		}
283 
284 		Vector<uint8_t> data;
285 		data.resize(info.uncompressed_size);
286 
287 		//read
288 		unzOpenCurrentFile(pkg);
289 		unzReadCurrentFile(pkg, data.ptrw(), data.size());
290 		unzCloseCurrentFile(pkg);
291 
292 		String base_dir = file_path.get_base_dir().trim_suffix("/");
293 
294 		if (base_dir != contents_dir && base_dir.begins_with(contents_dir)) {
295 			base_dir = base_dir.substr(contents_dir.length(), file_path.length()).trim_prefix("/");
296 			file = base_dir.plus_file(file);
297 
298 			DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
299 			ERR_CONTINUE(!da);
300 
301 			String output_dir = template_path.plus_file(base_dir);
302 
303 			if (!DirAccess::exists(output_dir)) {
304 				Error mkdir_err = da->make_dir_recursive(output_dir);
305 				ERR_CONTINUE(mkdir_err != OK);
306 			}
307 		}
308 
309 		if (p) {
310 			p->step(TTR("Importing:") + " " + file, fc);
311 		}
312 
313 		String to_write = template_path.plus_file(file);
314 		FileAccessRef f = FileAccess::open(to_write, FileAccess::WRITE);
315 
316 		if (!f) {
317 			ret = unzGoToNextFile(pkg);
318 			fc++;
319 			ERR_CONTINUE_MSG(true, "Can't open file from path '" + String(to_write) + "'.");
320 		}
321 
322 		f->store_buffer(data.ptr(), data.size());
323 
324 #ifndef WINDOWS_ENABLED
325 		FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF);
326 #endif
327 
328 		ret = unzGoToNextFile(pkg);
329 		fc++;
330 	}
331 
332 	if (p) {
333 		memdelete(p);
334 	}
335 
336 	unzClose(pkg);
337 
338 	_update_template_list();
339 	return true;
340 }
341 
popup_manager()342 void ExportTemplateManager::popup_manager() {
343 
344 	_update_template_list();
345 	popup_centered_minsize(Size2(400, 400) * EDSCALE);
346 }
347 
ok_pressed()348 void ExportTemplateManager::ok_pressed() {
349 
350 	template_open->popup_centered_ratio();
351 }
352 
_http_download_mirror_completed(int p_status,int p_code,const PoolStringArray & headers,const PoolByteArray & p_data)353 void ExportTemplateManager::_http_download_mirror_completed(int p_status, int p_code, const PoolStringArray &headers, const PoolByteArray &p_data) {
354 
355 	if (p_status != HTTPRequest::RESULT_SUCCESS || p_code != 200) {
356 		EditorNode::get_singleton()->show_warning(TTR("Error getting the list of mirrors."));
357 		return;
358 	}
359 
360 	String mirror_str;
361 	{
362 		PoolByteArray::Read r = p_data.read();
363 		mirror_str.parse_utf8((const char *)r.ptr(), p_data.size());
364 	}
365 
366 	template_list_state->hide();
367 	template_download_progress->hide();
368 
369 	Variant r;
370 	String errs;
371 	int errline;
372 	Error err = JSON::parse(mirror_str, r, errs, errline);
373 	if (err != OK) {
374 		EditorNode::get_singleton()->show_warning(TTR("Error parsing JSON of mirror list. Please report this issue!"));
375 		return;
376 	}
377 
378 	bool mirrors_found = false;
379 
380 	Dictionary d = r;
381 	if (d.has("mirrors")) {
382 		Array mirrors = d["mirrors"];
383 		for (int i = 0; i < mirrors.size(); i++) {
384 			Dictionary m = mirrors[i];
385 			ERR_CONTINUE(!m.has("url") || !m.has("name"));
386 			LinkButton *lb = memnew(LinkButton);
387 			lb->set_text(m["name"]);
388 			lb->connect("pressed", this, "_begin_template_download", varray(m["url"]));
389 			template_list->add_child(lb);
390 			mirrors_found = true;
391 		}
392 	}
393 
394 	if (!mirrors_found) {
395 		EditorNode::get_singleton()->show_warning(TTR("No download links found for this version. Direct download is only available for official releases."));
396 		return;
397 	}
398 }
_http_download_templates_completed(int p_status,int p_code,const PoolStringArray & headers,const PoolByteArray & p_data)399 void ExportTemplateManager::_http_download_templates_completed(int p_status, int p_code, const PoolStringArray &headers, const PoolByteArray &p_data) {
400 
401 	switch (p_status) {
402 
403 		case HTTPRequest::RESULT_CANT_RESOLVE: {
404 			template_list_state->set_text(TTR("Can't resolve."));
405 		} break;
406 		case HTTPRequest::RESULT_BODY_SIZE_LIMIT_EXCEEDED:
407 		case HTTPRequest::RESULT_CONNECTION_ERROR:
408 		case HTTPRequest::RESULT_CHUNKED_BODY_SIZE_MISMATCH:
409 		case HTTPRequest::RESULT_SSL_HANDSHAKE_ERROR:
410 		case HTTPRequest::RESULT_CANT_CONNECT: {
411 			template_list_state->set_text(TTR("Can't connect."));
412 		} break;
413 		case HTTPRequest::RESULT_NO_RESPONSE: {
414 			template_list_state->set_text(TTR("No response."));
415 		} break;
416 		case HTTPRequest::RESULT_REQUEST_FAILED: {
417 			template_list_state->set_text(TTR("Request Failed."));
418 		} break;
419 		case HTTPRequest::RESULT_REDIRECT_LIMIT_REACHED: {
420 			template_list_state->set_text(TTR("Redirect Loop."));
421 		} break;
422 		default: {
423 			if (p_code != 200) {
424 				template_list_state->set_text(TTR("Failed:") + " " + itos(p_code));
425 			} else {
426 				String path = download_templates->get_download_file();
427 				template_list_state->set_text(TTR("Download Complete."));
428 				template_downloader->hide();
429 				bool ret = _install_from_file(path, false);
430 				if (ret) {
431 					// Clean up downloaded file.
432 					DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
433 					Error err = da->remove(path);
434 					if (err != OK) {
435 						EditorNode::get_singleton()->add_io_error(TTR("Cannot remove temporary file:") + "\n" + path + "\n");
436 					}
437 				} else {
438 					EditorNode::get_singleton()->add_io_error(vformat(TTR("Templates installation failed.\nThe problematic templates archives can be found at '%s'."), path));
439 				}
440 			}
441 		} break;
442 	}
443 
444 	set_process(false);
445 }
446 
_begin_template_download(const String & p_url)447 void ExportTemplateManager::_begin_template_download(const String &p_url) {
448 
449 	if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
450 		OS::get_singleton()->shell_open(p_url);
451 		return;
452 	}
453 
454 	for (int i = 0; i < template_list->get_child_count(); i++) {
455 		BaseButton *b = Object::cast_to<BaseButton>(template_list->get_child(0));
456 		if (b) {
457 			b->set_disabled(true);
458 		}
459 	}
460 
461 	download_data.clear();
462 	download_templates->set_download_file(EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_templates.tpz"));
463 	download_templates->set_use_threads(true);
464 
465 	Error err = download_templates->request(p_url);
466 	if (err != OK) {
467 		EditorNode::get_singleton()->show_warning(TTR("Error requesting URL:") + " " + p_url);
468 		return;
469 	}
470 
471 	set_process(true);
472 
473 	template_list_state->show();
474 	template_download_progress->set_max(100);
475 	template_download_progress->set_value(0);
476 	template_download_progress->show();
477 	template_list_state->set_text(TTR("Connecting to Mirror..."));
478 }
479 
_window_template_downloader_closed()480 void ExportTemplateManager::_window_template_downloader_closed() {
481 	download_templates->cancel_request();
482 }
483 
_notification(int p_what)484 void ExportTemplateManager::_notification(int p_what) {
485 
486 	if (p_what == NOTIFICATION_PROCESS) {
487 
488 		update_countdown -= get_process_delta_time();
489 
490 		if (update_countdown > 0) {
491 			return;
492 		}
493 		update_countdown = 0.5;
494 		String status;
495 		bool errored = false;
496 
497 		switch (download_templates->get_http_client_status()) {
498 			case HTTPClient::STATUS_DISCONNECTED:
499 				status = TTR("Disconnected");
500 				errored = true;
501 				break;
502 			case HTTPClient::STATUS_RESOLVING: status = TTR("Resolving"); break;
503 			case HTTPClient::STATUS_CANT_RESOLVE:
504 				status = TTR("Can't Resolve");
505 				errored = true;
506 				break;
507 			case HTTPClient::STATUS_CONNECTING: status = TTR("Connecting..."); break;
508 			case HTTPClient::STATUS_CANT_CONNECT:
509 				status = TTR("Can't Connect");
510 				errored = true;
511 				break;
512 			case HTTPClient::STATUS_CONNECTED: status = TTR("Connected"); break;
513 			case HTTPClient::STATUS_REQUESTING: status = TTR("Requesting..."); break;
514 			case HTTPClient::STATUS_BODY:
515 				status = TTR("Downloading");
516 				if (download_templates->get_body_size() > 0) {
517 					status += " " + String::humanize_size(download_templates->get_downloaded_bytes()) + "/" + String::humanize_size(download_templates->get_body_size());
518 					template_download_progress->set_max(download_templates->get_body_size());
519 					template_download_progress->set_value(download_templates->get_downloaded_bytes());
520 				} else {
521 					status += " " + String::humanize_size(download_templates->get_downloaded_bytes());
522 				}
523 				break;
524 			case HTTPClient::STATUS_CONNECTION_ERROR:
525 				status = TTR("Connection Error");
526 				errored = true;
527 				break;
528 			case HTTPClient::STATUS_SSL_HANDSHAKE_ERROR:
529 				status = TTR("SSL Handshake Error");
530 				errored = true;
531 				break;
532 		}
533 
534 		template_list_state->set_text(status);
535 		if (errored) {
536 			set_process(false);
537 		}
538 	}
539 
540 	if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
541 		if (!is_visible_in_tree()) {
542 			set_process(false);
543 		}
544 	}
545 }
546 
can_install_android_template()547 bool ExportTemplateManager::can_install_android_template() {
548 
549 	const String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);
550 	return FileAccess::exists(templates_dir.plus_file("android_source.zip"));
551 }
552 
install_android_template()553 Error ExportTemplateManager::install_android_template() {
554 
555 	// To support custom Android builds, we install the Java source code and buildsystem
556 	// from android_source.zip to the project's res://android folder.
557 
558 	DirAccessRef da = DirAccess::open("res://");
559 	ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
560 
561 	// Make res://android dir (if it does not exist).
562 	da->make_dir("android");
563 	{
564 		// Add version, to ensure building won't work if template and Godot version don't match.
565 		FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::WRITE);
566 		ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
567 		f->store_line(VERSION_FULL_CONFIG);
568 		f->close();
569 	}
570 
571 	// Create the android plugins directory.
572 	Error err = da->make_dir_recursive("android/plugins");
573 	ERR_FAIL_COND_V(err != OK, err);
574 
575 	err = da->make_dir_recursive("android/build");
576 	ERR_FAIL_COND_V(err != OK, err);
577 	{
578 		// Add an empty .gdignore file to avoid scan.
579 		FileAccessRef f = FileAccess::open("res://android/build/.gdignore", FileAccess::WRITE);
580 		ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
581 		f->store_line("");
582 		f->close();
583 	}
584 
585 	// Uncompress source template.
586 
587 	const String &templates_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);
588 	const String &source_zip = templates_path.plus_file("android_source.zip");
589 	ERR_FAIL_COND_V(!FileAccess::exists(source_zip), ERR_CANT_OPEN);
590 
591 	FileAccess *src_f = NULL;
592 	zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
593 
594 	unzFile pkg = unzOpen2(source_zip.utf8().get_data(), &io);
595 	ERR_FAIL_COND_V_MSG(!pkg, ERR_CANT_OPEN, "Android sources not in ZIP format.");
596 
597 	int ret = unzGoToFirstFile(pkg);
598 	int total_files = 0;
599 	// Count files to unzip.
600 	while (ret == UNZ_OK) {
601 		total_files++;
602 		ret = unzGoToNextFile(pkg);
603 	}
604 	ret = unzGoToFirstFile(pkg);
605 
606 	ProgressDialog::get_singleton()->add_task("uncompress_src", TTR("Uncompressing Android Build Sources"), total_files);
607 
608 	Set<String> dirs_tested;
609 	int idx = 0;
610 	while (ret == UNZ_OK) {
611 
612 		// Get file path.
613 		unz_file_info info;
614 		char fpath[16384];
615 		ret = unzGetCurrentFileInfo(pkg, &info, fpath, 16384, NULL, 0, NULL, 0);
616 
617 		String path = fpath;
618 		String base_dir = path.get_base_dir();
619 
620 		if (!path.ends_with("/")) {
621 			Vector<uint8_t> data;
622 			data.resize(info.uncompressed_size);
623 
624 			// Read.
625 			unzOpenCurrentFile(pkg);
626 			unzReadCurrentFile(pkg, data.ptrw(), data.size());
627 			unzCloseCurrentFile(pkg);
628 
629 			if (!dirs_tested.has(base_dir)) {
630 				da->make_dir_recursive(String("android/build").plus_file(base_dir));
631 				dirs_tested.insert(base_dir);
632 			}
633 
634 			String to_write = String("res://android/build").plus_file(path);
635 			FileAccess *f = FileAccess::open(to_write, FileAccess::WRITE);
636 			if (f) {
637 				f->store_buffer(data.ptr(), data.size());
638 				memdelete(f);
639 #ifndef WINDOWS_ENABLED
640 				FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF);
641 #endif
642 			} else {
643 				ERR_PRINTS("Can't uncompress file: " + to_write);
644 			}
645 		}
646 
647 		ProgressDialog::get_singleton()->task_step("uncompress_src", path, idx);
648 
649 		idx++;
650 		ret = unzGoToNextFile(pkg);
651 	}
652 
653 	ProgressDialog::get_singleton()->end_task("uncompress_src");
654 	unzClose(pkg);
655 
656 	return OK;
657 }
658 
_bind_methods()659 void ExportTemplateManager::_bind_methods() {
660 
661 	ClassDB::bind_method("_download_template", &ExportTemplateManager::_download_template);
662 	ClassDB::bind_method("_uninstall_template", &ExportTemplateManager::_uninstall_template);
663 	ClassDB::bind_method("_uninstall_template_confirm", &ExportTemplateManager::_uninstall_template_confirm);
664 	ClassDB::bind_method("_install_from_file", &ExportTemplateManager::_install_from_file);
665 	ClassDB::bind_method("_http_download_mirror_completed", &ExportTemplateManager::_http_download_mirror_completed);
666 	ClassDB::bind_method("_http_download_templates_completed", &ExportTemplateManager::_http_download_templates_completed);
667 	ClassDB::bind_method("_begin_template_download", &ExportTemplateManager::_begin_template_download);
668 	ClassDB::bind_method("_window_template_downloader_closed", &ExportTemplateManager::_window_template_downloader_closed);
669 }
670 
ExportTemplateManager()671 ExportTemplateManager::ExportTemplateManager() {
672 
673 	VBoxContainer *main_vb = memnew(VBoxContainer);
674 	add_child(main_vb);
675 
676 	current_hb = memnew(HBoxContainer);
677 	main_vb->add_margin_child(TTR("Current Version:"), current_hb, false);
678 
679 	installed_scroll = memnew(ScrollContainer);
680 	main_vb->add_margin_child(TTR("Installed Versions:"), installed_scroll, true);
681 
682 	installed_vb = memnew(VBoxContainer);
683 	installed_scroll->add_child(installed_vb);
684 	installed_scroll->set_enable_v_scroll(true);
685 	installed_scroll->set_enable_h_scroll(false);
686 	installed_vb->set_h_size_flags(SIZE_EXPAND_FILL);
687 
688 	get_cancel()->set_text(TTR("Close"));
689 	get_ok()->set_text(TTR("Install From File"));
690 
691 	remove_confirm = memnew(ConfirmationDialog);
692 	remove_confirm->set_title(TTR("Remove Template"));
693 	add_child(remove_confirm);
694 	remove_confirm->connect("confirmed", this, "_uninstall_template_confirm");
695 
696 	template_open = memnew(FileDialog);
697 	template_open->set_title(TTR("Select Template File"));
698 	template_open->add_filter("*.tpz ; " + TTR("Godot Export Templates"));
699 	template_open->set_access(FileDialog::ACCESS_FILESYSTEM);
700 	template_open->set_mode(FileDialog::MODE_OPEN_FILE);
701 	template_open->connect("file_selected", this, "_install_from_file", varray(true));
702 	add_child(template_open);
703 
704 	set_title(TTR("Export Template Manager"));
705 	set_hide_on_ok(false);
706 
707 	request_mirror = memnew(HTTPRequest);
708 	add_child(request_mirror);
709 	request_mirror->connect("request_completed", this, "_http_download_mirror_completed");
710 
711 	download_templates = memnew(HTTPRequest);
712 	add_child(download_templates);
713 	download_templates->connect("request_completed", this, "_http_download_templates_completed");
714 
715 	template_downloader = memnew(AcceptDialog);
716 	template_downloader->set_title(TTR("Download Templates"));
717 	template_downloader->get_ok()->set_text(TTR("Close"));
718 	template_downloader->set_exclusive(true);
719 	add_child(template_downloader);
720 	template_downloader->connect("popup_hide", this, "_window_template_downloader_closed");
721 
722 	VBoxContainer *vbc = memnew(VBoxContainer);
723 	template_downloader->add_child(vbc);
724 	ScrollContainer *sc = memnew(ScrollContainer);
725 	sc->set_custom_minimum_size(Size2(400, 200) * EDSCALE);
726 	vbc->add_margin_child(TTR("Select mirror from list: (Shift+Click: Open in Browser)"), sc);
727 	template_list = memnew(VBoxContainer);
728 	sc->add_child(template_list);
729 	sc->set_enable_v_scroll(true);
730 	sc->set_enable_h_scroll(false);
731 	template_list_state = memnew(Label);
732 	vbc->add_child(template_list_state);
733 	template_download_progress = memnew(ProgressBar);
734 	vbc->add_child(template_download_progress);
735 
736 	update_countdown = 0;
737 }
738